mirror of
https://github.com/laurent22/joplin.git
synced 2025-03-29 21:21:15 +02:00
Server: Temporarily save user info before deleting account
This commit is contained in:
parent
a4e279270b
commit
68469bc1a5
Binary file not shown.
@ -0,0 +1,17 @@
|
||||
import { Knex } from 'knex';
|
||||
import { DbConnection } from '../db';
|
||||
|
||||
export async function up(db: DbConnection): Promise<any> {
|
||||
await db.schema.createTable('backup_items', (table: Knex.CreateTableBuilder) => {
|
||||
table.increments('id').unique().primary().notNullable();
|
||||
table.integer('type').notNullable();
|
||||
table.text('key', 'mediumtext').notNullable();
|
||||
table.string('user_id', 32).defaultTo('').notNullable();
|
||||
table.binary('content').notNullable();
|
||||
table.bigInteger('created_time').notNullable();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(db: DbConnection): Promise<any> {
|
||||
await db.schema.dropTable('backup_items');
|
||||
}
|
29
packages/server/src/models/BackupItemModel.ts
Normal file
29
packages/server/src/models/BackupItemModel.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { BackupItem, BackupItemType } from '../services/database/types';
|
||||
import BaseModel from './BaseModel';
|
||||
|
||||
export default class BackupItemModel extends BaseModel<BackupItem> {
|
||||
|
||||
protected get tableName(): string {
|
||||
return 'backup_items';
|
||||
}
|
||||
|
||||
protected hasUuid(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected hasUpdatedTime(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async add(type: BackupItemType, key: string, content: any, userId: string = ''): Promise<BackupItem> {
|
||||
const item: BackupItem = {
|
||||
user_id: userId,
|
||||
key,
|
||||
type,
|
||||
content,
|
||||
};
|
||||
|
||||
return this.save(item);
|
||||
}
|
||||
|
||||
}
|
@ -166,6 +166,10 @@ export default abstract class BaseModel<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected hasUpdatedTime(): boolean {
|
||||
return this.autoTimestampEnabled();
|
||||
}
|
||||
|
||||
protected get hasParentId(): boolean {
|
||||
return false;
|
||||
}
|
||||
@ -315,7 +319,7 @@ export default abstract class BaseModel<T> {
|
||||
if (isNew) {
|
||||
(toSave as WithDates).created_time = timestamp;
|
||||
}
|
||||
(toSave as WithDates).updated_time = timestamp;
|
||||
if (this.hasUpdatedTime()) (toSave as WithDates).updated_time = timestamp;
|
||||
}
|
||||
|
||||
if (options.skipValidation !== true) object = await this.validate(object, { isNew: isNew, rules: options.validationRules ? options.validationRules : {} });
|
||||
|
@ -75,6 +75,7 @@ import { Config } from '../utils/types';
|
||||
import LockModel from './LockModel';
|
||||
import StorageModel from './StorageModel';
|
||||
import UserDeletionModel from './UserDeletionModel';
|
||||
import BackupItemModel from './BackupItemModel';
|
||||
|
||||
export type NewModelFactoryHandler = (db: DbConnection)=> Models;
|
||||
|
||||
@ -170,6 +171,10 @@ export class Models {
|
||||
return new UserDeletionModel(this.db_, this.newModelFactory, this.config_);
|
||||
}
|
||||
|
||||
public backupItem() {
|
||||
return new BackupItemModel(this.db_, this.newModelFactory, this.config_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function newModelFactory(db: DbConnection, config: Config): Models {
|
||||
|
@ -2,6 +2,7 @@ import config from '../config';
|
||||
import { shareFolderWithUser } from '../utils/testing/shareApiUtils';
|
||||
import { afterAllTests, beforeAllDb, beforeEachDb, createNote, createUserAndSession, models } from '../utils/testing/testUtils';
|
||||
import { Env } from '../utils/types';
|
||||
import { BackupItemType } from './database/types';
|
||||
import UserDeletionService from './UserDeletionService';
|
||||
|
||||
const newService = () => {
|
||||
@ -70,6 +71,8 @@ describe('UserDeletionService', function() {
|
||||
expect(await models().user().count()).toBe(2);
|
||||
expect(await models().session().count()).toBe(2);
|
||||
|
||||
const beforeTime = Date.now();
|
||||
|
||||
const service = newService();
|
||||
await service.processDeletionJob(job, { sleepBetweenOperations: 0 });
|
||||
|
||||
@ -78,6 +81,18 @@ describe('UserDeletionService', function() {
|
||||
|
||||
const user = (await models().user().all())[0];
|
||||
expect(user.id).toBe(user2.id);
|
||||
|
||||
const backupItems = await models().backupItem().all();
|
||||
expect(backupItems.length).toBe(1);
|
||||
const backupItem = backupItems[0];
|
||||
expect(backupItem.key).toBe(user1.email);
|
||||
expect(backupItem.type).toBe(BackupItemType.UserAccount);
|
||||
expect(backupItem.created_time).toBeGreaterThanOrEqual(beforeTime);
|
||||
|
||||
const content = JSON.parse(backupItem.content.toString());
|
||||
expect(content.user.id).toBe(user1.id);
|
||||
expect(content.user.email).toBe(user1.email);
|
||||
expect(content.flags.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should not delete notebooks that are not owned', async function() {
|
||||
|
@ -33,6 +33,10 @@ export enum EventType {
|
||||
TaskCompleted = 2,
|
||||
}
|
||||
|
||||
export enum BackupItemType {
|
||||
UserAccount = 1,
|
||||
}
|
||||
|
||||
export enum UserFlagType {
|
||||
FailedPaymentWarning = 1,
|
||||
FailedPaymentFinal = 2,
|
||||
@ -87,6 +91,10 @@ export interface WithDates {
|
||||
created_time?: number;
|
||||
}
|
||||
|
||||
export interface WithCreatedDate {
|
||||
created_time?: number;
|
||||
}
|
||||
|
||||
export interface WithUuid {
|
||||
id?: Uuid;
|
||||
}
|
||||
@ -283,6 +291,14 @@ export interface Email extends WithDates {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export interface BackupItem extends WithCreatedDate {
|
||||
id?: number;
|
||||
type?: number;
|
||||
key?: string;
|
||||
user_id?: Uuid;
|
||||
content?: Buffer;
|
||||
}
|
||||
|
||||
export const databaseSchema: DatabaseTables = {
|
||||
sessions: {
|
||||
id: { type: 'string' },
|
||||
@ -478,5 +494,13 @@ export const databaseSchema: DatabaseTables = {
|
||||
created_time: { type: 'string' },
|
||||
key: { type: 'string' },
|
||||
},
|
||||
backup_items: {
|
||||
id: { type: 'number' },
|
||||
type: { type: 'number' },
|
||||
key: { type: 'string' },
|
||||
user_id: { type: 'string' },
|
||||
content: { type: 'any' },
|
||||
created_time: { type: 'string' },
|
||||
},
|
||||
};
|
||||
// AUTO-GENERATED-TYPES
|
||||
|
@ -36,6 +36,7 @@ const config = {
|
||||
'main.users': 'WithDates, WithUuid',
|
||||
'main.events': 'WithUuid',
|
||||
'main.user_deletions': 'WithDates',
|
||||
'main.backup_items': 'WithCreatedDate',
|
||||
},
|
||||
};
|
||||
|
||||
@ -63,6 +64,7 @@ const propertyTypes: Record<string, string> = {
|
||||
'user_deletions.end_time': 'number',
|
||||
'user_deletions.scheduled_time': 'number',
|
||||
'users.disabled_time': 'number',
|
||||
'backup_items.content': 'Buffer',
|
||||
};
|
||||
|
||||
function insertContentIntoFile(filePath: string, markerOpen: string, markerClose: string, contentToInsert: string): void {
|
||||
@ -93,6 +95,10 @@ function createTypeString(table: any) {
|
||||
if (['created_time', 'updated_time'].includes(name)) continue;
|
||||
}
|
||||
|
||||
if (table.extends && table.extends.indexOf('WithCreatedDate') >= 0) {
|
||||
if (['created_time'].includes(name)) continue;
|
||||
}
|
||||
|
||||
if (table.extends && table.extends.indexOf('WithUuid') >= 0) {
|
||||
if (['id'].includes(name)) continue;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user