77Code related to permanently deleting projects.
88*/
99
10+ import getLogger from "@cocalc/backend/logger" ;
11+ import getPool from "@cocalc/database/pool" ;
1012import { callback2 } from "@cocalc/util/async-utils" ;
1113import { PostgreSQL } from "./types" ;
14+ import { minutes_ago } from "@cocalc/util/misc" ;
15+ import { getServerSettings } from "@cocalc/database/settings" ;
16+ import { KUCALC_ON_PREMISES } from "@cocalc/util/db-schema/site-defaults" ;
17+
18+ const log = getLogger ( "db:delete-projects" ) ;
1219
1320/*
1421Permanently delete from the database all project records, where the
@@ -20,7 +27,7 @@ later then purges these projects from disk as well as the database.
2027*/
2128export async function permanently_unlink_all_deleted_projects_of_user (
2229 db : PostgreSQL ,
23- account_id_or_email_address : string
30+ account_id_or_email_address : string ,
2431) : Promise < void > {
2532 // Get the account_id if necessary.
2633 const account_id = await get_account_id ( db , account_id_or_email_address ) ;
@@ -36,7 +43,7 @@ export async function permanently_unlink_all_deleted_projects_of_user(
3643
3744async function get_account_id (
3845 db : PostgreSQL ,
39- account_id_or_email_address : string
46+ account_id_or_email_address : string ,
4047) : Promise < string > {
4148 if ( account_id_or_email_address . indexOf ( "@" ) == - 1 ) {
4249 return account_id_or_email_address ;
@@ -57,7 +64,7 @@ Another task has to run to actually get rid of the data, etc.
5764*/
5865export async function unlink_old_deleted_projects (
5966 db : PostgreSQL ,
60- age_d = 30
67+ age_d = 30 ,
6168) : Promise < void > {
6269 await callback2 ( db . _query , {
6370 query : "UPDATE projects" ,
@@ -69,3 +76,77 @@ export async function unlink_old_deleted_projects(
6976 ] ,
7077 } ) ;
7178}
79+
80+ const Q_CLEANUP_SYNCSTRINGS = `
81+ SELECT p.project_id, s.string_id
82+ FROM projects as p
83+ INNER JOIN syncstrings as s
84+ ON p.project_id = s.project_id
85+ WHERE p.deleted = true
86+ AND p.state ->> 'state' != 'deleted'
87+ ` ;
88+
89+ /*
90+ This is more thorough than the above. It issues actual delete operations on data of projects marked as deleted.
91+ When done, it sets the state.state to "deleted".
92+
93+ The operations involves deleting all syncstrings of that project (and associated with that, patches),
94+ and only for on-prem setups, it also deletes all the data stored in the project on disk.
95+
96+ This function is called every couple of hours. Hence ensure it does not run longer than the given max_run_m time (minutes)
97+ */
98+ export async function cleanup_old_projects_data (
99+ db : PostgreSQL ,
100+ delay_ms = 50 ,
101+ max_run_m = 60 ,
102+ ) {
103+ const settings = await getServerSettings ( ) ;
104+ const on_prem = settings . kucalc === KUCALC_ON_PREMISES ;
105+
106+ log . debug ( "cleanup_old_projects_data" , { delay_ms, max_run_m, on_prem } ) ;
107+ const start_ts = new Date ( ) ;
108+
109+ const pool = getPool ( ) ;
110+ const { rows } = await pool . query ( Q_CLEANUP_SYNCSTRINGS ) ;
111+
112+ let num = 0 ;
113+ let pid = "" ;
114+
115+ for ( const row of rows ) {
116+ const { project_id, string_id } = row ;
117+ if ( start_ts < minutes_ago ( max_run_m ) ) {
118+ log . debug (
119+ `cleanup_old_projects_data: too much time elapsed, breaking after ${ num } syncstrings` ,
120+ ) ;
121+ break ;
122+ }
123+
124+ log . debug (
125+ `cleanup_old_projects_data: deleting syncstring ${ project_id } /${ string_id } ` ,
126+ ) ;
127+ num += 1 ;
128+ await callback2 ( db . delete_syncstring , { string_id } ) ;
129+
130+ // wait for the given amount of delay_ms millio seconds
131+ await new Promise ( ( done ) => setTimeout ( done , delay_ms ) ) ;
132+
133+ if ( pid != project_id ) {
134+ pid = project_id ;
135+ if ( on_prem ) {
136+ log . debug (
137+ `cleanup_old_projects_data: deleting project data in ${ project_id } ` ,
138+ ) ;
139+ // TODO: this only works on-prem, and requires the project files to be mounted
140+
141+ log . debug ( `deleting all shared files in project ${ project_id } ` ) ;
142+ // TODO: do it directly like above, and also get rid of all those shares in the database
143+ }
144+
145+ // now, that we're done with that project, mark it as state.state ->> 'deleted'
146+ await callback2 ( db . set_project_state , {
147+ project_id,
148+ state : "deleted" ,
149+ } ) ;
150+ }
151+ }
152+ }
0 commit comments