mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
@@ -0,0 +1,96 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import ava, { TestFn } from 'ava';
|
||||
|
||||
import { Config } from '../../base/config';
|
||||
import { type User, UserModel } from '../../models/user';
|
||||
import { UserDocModel } from '../../models/user-doc';
|
||||
import { createTestingModule, type TestingModule } from '../utils';
|
||||
|
||||
interface Context {
|
||||
config: Config;
|
||||
module: TestingModule;
|
||||
user: UserModel;
|
||||
doc: UserDocModel;
|
||||
}
|
||||
|
||||
const test = ava as TestFn<Context>;
|
||||
|
||||
test.before(async t => {
|
||||
const module = await createTestingModule();
|
||||
|
||||
t.context.user = module.get(UserModel);
|
||||
t.context.doc = module.get(UserDocModel);
|
||||
t.context.config = module.get(Config);
|
||||
t.context.module = module;
|
||||
});
|
||||
|
||||
let user: User;
|
||||
|
||||
test.beforeEach(async t => {
|
||||
await t.context.module.initTestingDB();
|
||||
user = await t.context.user.create({
|
||||
email: 'test@affine.pro',
|
||||
});
|
||||
});
|
||||
|
||||
test.after(async t => {
|
||||
await t.context.module.close();
|
||||
});
|
||||
|
||||
test('should upsert a doc', async t => {
|
||||
const docId = randomUUID();
|
||||
const doc = await t.context.doc.upsert({
|
||||
spaceId: user.id,
|
||||
docId,
|
||||
blob: Buffer.from('hello'),
|
||||
timestamp: Date.now(),
|
||||
editorId: user.id,
|
||||
});
|
||||
t.truthy(doc.updatedAt);
|
||||
// add a new one
|
||||
const docId2 = randomUUID();
|
||||
const doc2 = await t.context.doc.upsert({
|
||||
spaceId: user.id,
|
||||
docId: docId2,
|
||||
blob: Buffer.from('world'),
|
||||
timestamp: Date.now(),
|
||||
editorId: user.id,
|
||||
});
|
||||
t.truthy(doc2.updatedAt);
|
||||
// update the first one
|
||||
const doc3 = await t.context.doc.upsert({
|
||||
spaceId: user.id,
|
||||
docId,
|
||||
blob: Buffer.from('world'),
|
||||
timestamp: Date.now() + 1000,
|
||||
editorId: user.id,
|
||||
});
|
||||
t.truthy(doc3.updatedAt);
|
||||
t.true(doc3.updatedAt > doc.updatedAt);
|
||||
// get all docs timestamps
|
||||
const timestamps = await t.context.doc.findTimestampsByUserId(user.id);
|
||||
t.deepEqual(timestamps, {
|
||||
[docId]: doc3.updatedAt.getTime(),
|
||||
[docId2]: doc2.updatedAt.getTime(),
|
||||
});
|
||||
});
|
||||
|
||||
test('should get a doc', async t => {
|
||||
const docId = randomUUID();
|
||||
const doc = await t.context.doc.upsert({
|
||||
spaceId: user.id,
|
||||
docId,
|
||||
blob: Buffer.from('hello'),
|
||||
timestamp: Date.now(),
|
||||
editorId: user.id,
|
||||
});
|
||||
t.truthy(doc.updatedAt);
|
||||
const doc2 = await t.context.doc.get(user.id, docId);
|
||||
t.truthy(doc2);
|
||||
t.is(doc2!.docId, docId);
|
||||
t.deepEqual(doc2!.blob, Buffer.from('hello'));
|
||||
// get a non-exist doc
|
||||
const doc3 = await t.context.doc.get(user.id, randomUUID());
|
||||
t.is(doc3, null);
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import { PageModel } from './page';
|
||||
import { MODELS_SYMBOL } from './provider';
|
||||
import { SessionModel } from './session';
|
||||
import { UserModel } from './user';
|
||||
import { UserDocModel } from './user-doc';
|
||||
import { UserFeatureModel } from './user-feature';
|
||||
import { VerificationTokenModel } from './verification-token';
|
||||
import { WorkspaceModel } from './workspace';
|
||||
@@ -28,6 +29,7 @@ const MODELS = {
|
||||
userFeature: UserFeatureModel,
|
||||
workspaceFeature: WorkspaceFeatureModel,
|
||||
doc: DocModel,
|
||||
userDoc: UserDocModel,
|
||||
};
|
||||
|
||||
type ModelsType = {
|
||||
@@ -85,6 +87,7 @@ export * from './feature';
|
||||
export * from './page';
|
||||
export * from './session';
|
||||
export * from './user';
|
||||
export * from './user-doc';
|
||||
export * from './user-feature';
|
||||
export * from './verification-token';
|
||||
export * from './workspace';
|
||||
|
||||
116
packages/backend/server/src/models/user-doc.ts
Normal file
116
packages/backend/server/src/models/user-doc.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { BaseModel } from './base';
|
||||
import { Doc } from './common';
|
||||
|
||||
/**
|
||||
* User Doc Model
|
||||
*/
|
||||
@Injectable()
|
||||
export class UserDocModel extends BaseModel {
|
||||
async upsert(doc: Doc) {
|
||||
const row = await this.db.userSnapshot.upsert({
|
||||
where: {
|
||||
userId_id: {
|
||||
userId: doc.spaceId,
|
||||
id: doc.docId,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
blob: doc.blob,
|
||||
updatedAt: new Date(doc.timestamp),
|
||||
},
|
||||
create: {
|
||||
userId: doc.spaceId,
|
||||
id: doc.docId,
|
||||
blob: doc.blob,
|
||||
createdAt: new Date(doc.timestamp),
|
||||
updatedAt: new Date(doc.timestamp),
|
||||
},
|
||||
select: {
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
return row;
|
||||
}
|
||||
|
||||
async get(userId: string, docId: string): Promise<Doc | null> {
|
||||
const row = await this.db.userSnapshot.findUnique({
|
||||
where: {
|
||||
userId_id: {
|
||||
userId,
|
||||
id: docId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
spaceId: row.userId,
|
||||
docId: row.id,
|
||||
blob: row.blob,
|
||||
timestamp: row.updatedAt.getTime(),
|
||||
editorId: row.userId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the timestamps of user docs by userId.
|
||||
*
|
||||
* @param after Only return timestamps after this timestamp.
|
||||
*/
|
||||
async findTimestampsByUserId(userId: string, after?: number) {
|
||||
const snapshots = await this.db.userSnapshot.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
where: {
|
||||
userId,
|
||||
...(after
|
||||
? {
|
||||
updatedAt: {
|
||||
gt: new Date(after),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
const result: Record<string, number> = {};
|
||||
|
||||
snapshots.forEach(s => {
|
||||
result[s.id] = s.updatedAt.getTime();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user doc by userId and docId.
|
||||
*/
|
||||
async delete(userId: string, docId: string) {
|
||||
await this.db.userSnapshot.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
id: docId,
|
||||
},
|
||||
});
|
||||
this.logger.log(`Deleted user ${userId} doc ${docId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user docs by userId.
|
||||
*/
|
||||
async deleteAllByUserId(userId: string) {
|
||||
const { count } = await this.db.userSnapshot.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
this.logger.log(`Deleted user ${userId} ${count} docs`);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user