feat: sync albums and album users ()

This commit is contained in:
Jason Rasmussen 2025-05-21 15:35:32 -04:00 committed by GitHub
parent 58af574241
commit cd288533a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 2811 additions and 934 deletions

View file

@ -4,9 +4,11 @@ import { DateTime } from 'luxon';
import { createHash, randomBytes } from 'node:crypto';
import { Writable } from 'node:stream';
import { AssetFace } from 'src/database';
import { AssetJobStatus, Assets, DB, FaceSearch, Person, Sessions } from 'src/db';
import { AssetType, AssetVisibility, SourceType } from 'src/enum';
import { Albums, AssetJobStatus, Assets, DB, FaceSearch, Person, Sessions } from 'src/db';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetType, AssetVisibility, SourceType, SyncRequestType } from 'src/enum';
import { ActivityRepository } from 'src/repositories/activity.repository';
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
import { AlbumRepository } from 'src/repositories/album.repository';
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
@ -28,8 +30,9 @@ import { UserRepository } from 'src/repositories/user.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
import { UserTable } from 'src/schema/tables/user.table';
import { BaseService } from 'src/services/base.service';
import { SyncService } from 'src/services/sync.service';
import { RepositoryInterface } from 'src/types';
import { newDate, newEmbedding, newUuid } from 'test/small.factory';
import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory';
import { automock, ServiceOverrides } from 'test/utils';
import { Mocked } from 'vitest';
@ -39,6 +42,7 @@ const sha256 = (value: string) => createHash('sha256').update(value).digest('bas
type RepositoriesTypes = {
activity: ActivityRepository;
album: AlbumRepository;
albumUser: AlbumUserRepository;
asset: AssetRepository;
assetJob: AssetJobRepository;
config: ConfigRepository;
@ -76,6 +80,61 @@ export type Context<R extends RepositoryOptions, S extends BaseService> = {
getRepository<T extends keyof RepositoriesTypes>(key: T): RepositoriesTypes[T];
};
export type SyncTestOptions = {
db: Kysely<DB>;
};
export const newSyncAuthUser = () => {
const user = mediumFactory.userInsert();
const session = mediumFactory.sessionInsert({ userId: user.id });
const auth = factory.auth({
session,
user: {
id: user.id,
name: user.name,
email: user.email,
},
});
return {
auth,
session,
user,
create: async (db: Kysely<DB>) => {
await new UserRepository(db).create(user);
await new SessionRepository(db).create(session);
},
};
};
export const newSyncTest = (options: SyncTestOptions) => {
const { sut, mocks, repos, getRepository } = newMediumService(SyncService, {
database: options.db,
repos: {
sync: 'real',
session: 'real',
},
});
const testSync = async (auth: AuthDto, types: SyncRequestType[]) => {
const stream = mediumFactory.syncStream();
// Wait for 2ms to ensure all updates are available and account for setTimeout inaccuracy
await new Promise((resolve) => setTimeout(resolve, 2));
await sut.stream(auth, stream, { types });
return stream.getResponse();
};
return {
sut,
mocks,
repos,
getRepository,
testSync,
};
};
export const newMediumService = <R extends RepositoryOptions, S extends BaseService>(
Service: ClassConstructor<S>,
options: {
@ -125,6 +184,14 @@ export const getRepository = <K extends keyof RepositoriesTypes>(key: K, db: Kys
return new ActivityRepository(db);
}
case 'album': {
return new AlbumRepository(db);
}
case 'albumUser': {
return new AlbumUserRepository(db);
}
case 'asset': {
return new AssetRepository(db);
}
@ -380,6 +447,19 @@ const assetInsert = (asset: Partial<Insertable<Assets>> = {}) => {
};
};
const albumInsert = (album: Partial<Insertable<Albums>> & { ownerId: string }) => {
const id = album.id || newUuid();
const defaults: Omit<Insertable<Albums>, 'ownerId'> = {
albumName: 'Album',
};
return {
...defaults,
...album,
id,
};
};
const faceInsert = (face: Partial<Insertable<FaceSearch>> & { faceId: string }) => {
const defaults = {
faceId: face.faceId,
@ -502,6 +582,7 @@ export const mediumFactory = {
assetInsert,
assetFaceInsert,
assetJobStatusInsert,
albumInsert,
faceInsert,
personInsert,
sessionInsert,