@@ -16,17 +16,30 @@ const { sendUpdate } = require("../ws");
1616const router = express . Router ( ) ;
1717const TaskManager = require ( "../classes/task-manager-singleton" ) ;
1818const TaskScheduler = require ( "../classes/task-scheduler-singleton" ) ;
19+ const { tables } = require ( "../global/backup_tables" ) ;
1920
2021// Database connection parameters
2122const postgresUser = process . env . POSTGRES_USER ;
2223const postgresPassword = process . env . POSTGRES_PASSWORD ;
2324const postgresIp = process . env . POSTGRES_IP ;
2425const postgresPort = process . env . POSTGRES_PORT ;
2526const postgresDatabase = process . env . POSTGRES_DB || "jfstat" ;
26- const postgresSslRejectUnauthorized = process . env . POSTGRES_SSL_REJECT_UNAUTHORIZED === undefined ? true : process . env . POSTGRES_SSL_REJECT_UNAUTHORIZED === "true" ;
27+ const postgresSslRejectUnauthorized =
28+ process . env . POSTGRES_SSL_REJECT_UNAUTHORIZED === undefined ? true : process . env . POSTGRES_SSL_REJECT_UNAUTHORIZED === "true" ;
2729
2830const backupfolder = "backup-data" ;
2931
32+ // table mappers
33+ const jf_libraries = require ( "../models/jf_libraries" ) ;
34+ const jf_library_items = require ( "../models/jf_library_items" ) ;
35+ const jf_library_seasons = require ( "../models/jf_library_seasons" ) ;
36+ const jf_library_episodes = require ( "../models/jf_library_episodes" ) ;
37+ const jf_users = require ( "../models/jf_users" ) ;
38+ const jf_playback_activity = require ( "../models/jf_playback_activity" ) ;
39+ const jf_playback_reporting_plugin_data = require ( "../models/jf_playback_reporting_plugin_data" ) ;
40+ const jf_item_info = require ( "../models/jf_item_info" ) ;
41+ //
42+ const db = require ( "../db" ) ;
3043// Restore function
3144
3245function readFile ( path ) {
@@ -63,16 +76,49 @@ function getBirthtimeFallback(fileStats, fileName) {
6376 // 2digits('-' or ':')2digits('-' or ':')2digits
6477 const regexp = / ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) [ _ T ] ( \d { 2 } ) [ - : ] ( \d { 2 } ) [ - : ] ( \d { 2 } ) / ;
6578 const matches = fileName . match ( regexp ) ;
66- if ( ! matches )
67- return null ;
79+ if ( ! matches ) return null ;
6880
6981 // Verify that each regex match is a valid number
70- for ( var i = 1 ; i < 7 ; i ++ ) {
71- if ( Number . isNaN ( Number ( matches [ i ] ) ) )
82+ for ( var i = 1 ; i < 7 ; i ++ ) {
83+ if ( Number . isNaN ( Number ( matches [ i ] ) ) ) return null ;
84+ }
85+
86+ return new Date ( matches [ 1 ] , matches [ 2 ] - 1 , matches [ 3 ] , matches [ 4 ] , matches [ 5 ] , matches [ 6 ] ) ;
87+ }
88+
89+ function getTableColumns ( tableName ) {
90+ switch ( tableName ) {
91+ case "jf_libraries" :
92+ return jf_libraries . jf_libraries_columns ;
93+ case "jf_library_items" :
94+ return jf_library_items . jf_library_items_columns ;
95+ case "jf_library_seasons" :
96+ return jf_library_seasons . jf_library_seasons_columns ;
97+ case "jf_library_episodes" :
98+ return jf_library_episodes . jf_library_episodes_columns ;
99+ case "jf_users" :
100+ return jf_users . jf_users_columns ;
101+ case "jf_playback_activity" :
102+ return jf_playback_activity . columnsPlayback ;
103+ case "jf_playback_reporting_plugin_data" :
104+ return jf_playback_reporting_plugin_data . columnsPlaybackReporting ;
105+ case "jf_item_info" :
106+ return jf_item_info . jf_item_info_columns ;
107+ default :
72108 return null ;
73109 }
110+ }
74111
75- return new Date ( matches [ 1 ] , matches [ 2 ] - 1 , matches [ 3 ] , matches [ 4 ] , matches [ 5 ] , matches [ 6 ] ) ;
112+ //fixes Bulk insert error: error: column "Genres" is of type jsonb but expression is of type text[]
113+ function formatData ( data ) {
114+ if ( ! Array . isArray ( data ) ) return data ;
115+ return data . map ( ( row ) => {
116+ const formatted = { } ;
117+ for ( const [ key , value ] of Object . entries ( row ) ) {
118+ formatted [ key ] = Array . isArray ( value ) ? JSON . stringify ( value ) : value ;
119+ }
120+ return formatted ;
121+ } ) ;
76122}
77123
78124async function restore ( file , refLog ) {
@@ -81,17 +127,6 @@ async function restore(file, refLog) {
81127 color : "yellow" ,
82128 Message : "Restoring from Backup: " + file ,
83129 } ) ;
84- const pool = new Pool ( {
85- user : postgresUser ,
86- password : postgresPassword ,
87- host : postgresIp ,
88- port : postgresPort ,
89- database : postgresDatabase ,
90- ...( process . env . POSTGRES_SSL_ENABLED === "true"
91- ? { ssl : { rejectUnauthorized : postgresSslRejectUnauthorized } }
92- : { } ) ,
93- } ) ;
94-
95130 const backupPath = file ;
96131
97132 let jsonData ;
@@ -102,7 +137,6 @@ async function restore(file, refLog) {
102137 } catch ( err ) {
103138 refLog . logData . push ( {
104139 color : "red" ,
105- key : tableName ,
106140 Message : `Failed to read backup file` ,
107141 } ) ;
108142 Logging . updateLog ( refLog . uuid , refLog . logData , taskstate . FAILED ) ;
@@ -114,38 +148,60 @@ async function restore(file, refLog) {
114148 console . log ( "No Data" ) ;
115149 return ;
116150 }
151+ const allowList = tables . map ( ( table ) => table . value ) ;
117152
153+ // Perform bulk, parameterized inserts per table to improve performance
118154 for ( let table of jsonData ) {
119155 const data = Object . values ( table ) [ 0 ] ;
120156 const tableName = Object . keys ( table ) [ 0 ] ;
157+
158+ if ( ! allowList . includes ( tableName ) ) {
159+ refLog . logData . push ( {
160+ color : "red" ,
161+ Message : `Table ${ tableName } is not allowed to be restored` ,
162+ } ) ;
163+ continue ;
164+ }
165+
166+ const tableColumns = getTableColumns ( tableName ) ;
167+ if ( ! tableColumns ) {
168+ refLog . logData . push ( {
169+ color : "red" ,
170+ Message : `No columns found for table ${ tableName } ` ,
171+ } ) ;
172+ continue ;
173+ }
174+
121175 refLog . logData . push ( {
122176 color : "dodgerblue" ,
123177 key : tableName ,
124178 Message : `Restoring ${ tableName } ` ,
125179 } ) ;
126- for ( let index in data ) {
127- const keysWithQuotes = Object . keys ( data [ index ] ) . map ( ( key ) => `"${ key } "` ) ;
128- const keyString = keysWithQuotes . join ( ", " ) ;
129-
130- const valuesWithQuotes = Object . values ( data [ index ] ) . map ( ( col ) => {
131- if ( col === null ) {
132- return "NULL" ;
133- } else if ( typeof col === "string" ) {
134- return `'${ col . replace ( / ' / g, "''" ) } '` ;
135- } else if ( typeof col === "object" ) {
136- return `'${ JSON . stringify ( col ) . replace ( / ' / g, "''" ) } '` ;
137- } else {
138- return `'${ col } '` ;
139- }
140- } ) ;
141180
142- const valueString = valuesWithQuotes . join ( ", " ) ;
181+ if ( data . length == 0 ) {
182+ refLog . logData . push ( {
183+ color : "yellow" ,
184+ key : tableName ,
185+ Message : `No data to restore for ${ tableName } ` ,
186+ } ) ;
187+ continue ;
188+ }
189+ let result = await db . insertBulk ( tableName , formatData ( data ) , tableColumns ) ;
143190
144- const query = `INSERT INTO ${ tableName } (${ keyString } ) VALUES(${ valueString } ) ON CONFLICT DO NOTHING` ;
145- const { rows } = await pool . query ( query ) ;
191+ if ( result . Result === "SUCCESS" ) {
192+ refLog . logData . push ( {
193+ color : "lawngreen" ,
194+ key : tableName ,
195+ Message : `Restored ${ data . length } rows to ${ tableName } ` ,
196+ } ) ;
197+ } else {
198+ refLog . logData . push ( {
199+ color : "red" ,
200+ key : tableName ,
201+ Message : `Failed to restore ${ tableName } : ${ result . message } ` ,
202+ } ) ;
146203 }
147204 }
148- await pool . end ( ) ;
149205 refLog . logData . push ( { color : "lawngreen" , Message : "Restore Complete" } ) ;
150206}
151207
0 commit comments