mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-27 10:32:58 +02:00
clean up
This commit is contained in:
parent
6d41787a29
commit
0b37e99132
@ -42,9 +42,6 @@ export default class FileApiDriverJoplinServer {
|
||||
isDeleted: isDeleted,
|
||||
};
|
||||
|
||||
// TODO - HANDLE DELETED
|
||||
// if (md['.tag'] === 'deleted') output.isDeleted = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,10 @@ export interface SaveOptions {
|
||||
trackChanges?: boolean;
|
||||
}
|
||||
|
||||
export interface LoadOptions {
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
export interface DeleteOptions {
|
||||
validationRules?: any;
|
||||
}
|
||||
@ -132,7 +136,7 @@ export default abstract class BaseModel<T> {
|
||||
|
||||
if (debugTransaction) console.info('START', name, txIndex);
|
||||
|
||||
let output:T = null;
|
||||
let output: T = null;
|
||||
|
||||
try {
|
||||
output = await fn();
|
||||
@ -252,15 +256,15 @@ export default abstract class BaseModel<T> {
|
||||
return toSave;
|
||||
}
|
||||
|
||||
public async loadByIds(ids: string[]): Promise<T[]> {
|
||||
public async loadByIds(ids: string[], options: LoadOptions = {}): Promise<T[]> {
|
||||
if (!ids.length) return [];
|
||||
return this.db(this.tableName).select(this.defaultFields).whereIn('id', ids);
|
||||
return this.db(this.tableName).select(options.fields || this.defaultFields).whereIn('id', ids);
|
||||
}
|
||||
|
||||
public async load(id: string): Promise<T> {
|
||||
public async load(id: string, options: LoadOptions = {}): Promise<T> {
|
||||
if (!id) throw new Error('id cannot be empty');
|
||||
|
||||
return this.db(this.tableName).select(this.defaultFields).where({ id: id }).first();
|
||||
return this.db(this.tableName).select(options.fields || this.defaultFields).where({ id: id }).first();
|
||||
}
|
||||
|
||||
public async delete(id: string | string[]): Promise<void> {
|
||||
|
@ -396,12 +396,31 @@ export default class FileModel extends BaseModel<File> {
|
||||
return file;
|
||||
}
|
||||
|
||||
// private async processFileLink(file: File): Promise<File> {
|
||||
// if (!('source_file_id' in file)) throw new Error('Cannot process a file without a source_file_id');
|
||||
// if (!file.source_file_id) return file;
|
||||
// const content = await this.loadContent(file.source_file_id, {
|
||||
// skipPermissionCheck: true,
|
||||
// // Current we don't follow links more than one level deep. In
|
||||
// // practice it means that if a user tries to share a note that has
|
||||
// // been shared with them, it will not work. Instead they'll have to
|
||||
// // make a copy of that note and share that. Anything else would
|
||||
// // probably be too complex to make any sense in terms of UI.
|
||||
// skipFollowLinks: true,
|
||||
// });
|
||||
// return { ...file, content };
|
||||
// }
|
||||
|
||||
// If the file is a link to another file, the content of the source if
|
||||
// assigned to the content of the destination.
|
||||
private async processFileLink(file: File): Promise<File> {
|
||||
if (!('source_file_id' in file)) throw new Error('Cannot process a file without a source_file_id');
|
||||
if (!file.source_file_id) return file;
|
||||
const content = await this.loadContent(file.source_file_id, {
|
||||
// assigned to the content of the destination. The updated_time property is
|
||||
// also set to the most recent one among the source and the dest.
|
||||
private async processFileLinks(files: File[]): Promise<File[]> {
|
||||
const sourceFileIds = files.filter(f => !!f.source_file_id).map(f => f.source_file_id);
|
||||
if (!sourceFileIds.length) return files;
|
||||
|
||||
const fields = Object.keys(files[0]);
|
||||
const sourceFiles = await this.loadByIds(sourceFileIds, {
|
||||
fields,
|
||||
skipPermissionCheck: true,
|
||||
// Current we don't follow links more than one level deep. In
|
||||
// practice it means that if a user tries to share a note that has
|
||||
@ -410,33 +429,42 @@ export default class FileModel extends BaseModel<File> {
|
||||
// probably be too complex to make any sense in terms of UI.
|
||||
skipFollowLinks: true,
|
||||
});
|
||||
return { ...file, content };
|
||||
}
|
||||
|
||||
private async loadContent(id: string, options: LoadOptions = {}): Promise<Buffer> {
|
||||
const file: File = await this.loadWithContent(id, { ...options, fields: ['id', 'content'] });
|
||||
return file.content;
|
||||
const modFiles = files.slice();
|
||||
for (let i = 0; i < modFiles.length; i++) {
|
||||
const file = modFiles[i];
|
||||
if (!file.source_file_id) continue;
|
||||
const sourceFile = sourceFiles.find(f => f.id === file.source_file_id);
|
||||
if (!sourceFile) {
|
||||
throw new Error(`File is linked to a file that no longer exists: ${file.id} => ${file.source_file_id}`);
|
||||
}
|
||||
|
||||
const modFile = { ...file };
|
||||
|
||||
if ('updated_time' in modFile) modFile.updated_time = Math.max(sourceFile.updated_time, file.updated_time);
|
||||
if ('content' in modFile) modFile.content = sourceFile.content;
|
||||
|
||||
modFiles[i] = modFile;
|
||||
}
|
||||
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public async loadWithContent(id: string, options: LoadOptions = {}): Promise<File> {
|
||||
const file: File = await this.db<File>(this.tableName).select(options.fields || '*').where({ id: id }).first();
|
||||
if (!file) return null;
|
||||
if (!options.skipPermissionCheck) await this.checkCanReadPermissions(file);
|
||||
return options.skipFollowLinks ? file : this.processFileLink(file);
|
||||
const fields = options.fields || this.defaultFields.concat(['content']);
|
||||
return this.load(id, { ...options, fields });
|
||||
}
|
||||
|
||||
public async loadByIds(ids: string[], options: LoadOptions = {}): Promise<File[]> {
|
||||
const files: File[] = await super.loadByIds(ids);
|
||||
const files: File[] = await super.loadByIds(ids, options);
|
||||
if (!files.length) return [];
|
||||
if (!options.skipPermissionCheck) await this.checkCanReadPermissions(files);
|
||||
return files;
|
||||
return options.skipFollowLinks ? files : this.processFileLinks(files);
|
||||
}
|
||||
|
||||
public async load(id: string, options: LoadOptions = {}): Promise<File> {
|
||||
const file: File = await super.load(id);
|
||||
if (!file) return null;
|
||||
if (!options.skipPermissionCheck) await this.checkCanReadPermissions(file);
|
||||
return file;
|
||||
const files = await this.loadByIds([id], options);
|
||||
return files.length ? files[0] : null;
|
||||
}
|
||||
|
||||
public async save(object: File, options: SaveOptions = {}): Promise<File> {
|
||||
|
@ -5,6 +5,7 @@ import { postApiC, postApi, getApiC, patchApi, getApi } from '../../utils/testin
|
||||
import { PaginatedFiles } from '../../models/FileModel';
|
||||
import { PaginatedChanges } from '../../models/ChangeModel';
|
||||
import { shareWithUserAndAccept } from '../../utils/testing/shareApiUtils';
|
||||
import { msleep } from '../../utils/time';
|
||||
|
||||
describe('api_shares', function() {
|
||||
|
||||
@ -87,6 +88,22 @@ describe('api_shares', function() {
|
||||
expect(fileContent.toString()).toBe('testing share');
|
||||
});
|
||||
|
||||
test('should get updated time of shared file', async function() {
|
||||
// If sharer changes the file, sharee should see the updated_time of the sharer file.
|
||||
const { user: user1, session: session1 } = await createUserAndSession(1);
|
||||
const { user: user2, session: session2 } = await createUserAndSession(2);
|
||||
|
||||
let { sharerFile, shareeFile } = await shareWithUserAndAccept(session1.id, user1, session2.id, user2);
|
||||
|
||||
await msleep(1);
|
||||
|
||||
await updateFile(user1.id, sharerFile.id, 'content modified');
|
||||
|
||||
sharerFile = await models().file({ userId: user1.id }).load(sharerFile.id);
|
||||
shareeFile = await models().file({ userId: user2.id }).load(shareeFile.id);
|
||||
expect(shareeFile.updated_time).toBe(sharerFile.updated_time);
|
||||
});
|
||||
|
||||
test('should see delta changes for linked files', async function() {
|
||||
const { user: user1, session: session1 } = await createUserAndSession(1);
|
||||
const { user: user2, session: session2 } = await createUserAndSession(2);
|
||||
@ -112,6 +129,8 @@ describe('api_shares', function() {
|
||||
cursor2 = page2.cursor;
|
||||
}
|
||||
|
||||
await msleep(1);
|
||||
|
||||
await updateFile(user1.id, sharerFile.id, 'from sharer');
|
||||
|
||||
{
|
||||
|
@ -33,7 +33,6 @@ router.get('shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const file = await fileModel.loadWithContent(share.file_id, { skipPermissionCheck: true });
|
||||
if (!file) throw new ErrorNotFound();
|
||||
|
||||
|
||||
const result = await renderFile(ctx, file, share);
|
||||
|
||||
ctx.response.body = result.body;
|
||||
|
@ -79,6 +79,33 @@ function initGlobalLogger() {
|
||||
Logger.initializeGlobalLogger(globalLogger);
|
||||
}
|
||||
|
||||
export function msleep(ms: number) {
|
||||
// It seems setTimeout can sometimes last less time than the provided
|
||||
// interval:
|
||||
//
|
||||
// https://stackoverflow.com/a/50912029/561309
|
||||
//
|
||||
// This can cause issues in tests where we expect the actual duration to be
|
||||
// the same as the provided interval or more, but not less. So the code
|
||||
// below check that the elapsed time is no less than the provided interval,
|
||||
// and if it is, it waits a bit longer.
|
||||
const startTime = Date.now();
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (Date.now() - startTime < ms) {
|
||||
const iid = setInterval(() => {
|
||||
if (Date.now() - startTime >= ms) {
|
||||
clearInterval(iid);
|
||||
resolve(null);
|
||||
}
|
||||
}, 2);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export async function koaAppContext(options: AppContextTestOptions = null): Promise<AppContext> {
|
||||
if (!db_) throw new Error('Database must be initialized first');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user