feat: sync auth user (#20067)

This commit is contained in:
Jason Rasmussen 2025-07-23 09:59:33 -04:00 committed by GitHub
parent ab597155fa
commit 92384c28de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 507 additions and 41 deletions

View file

@ -356,6 +356,7 @@ export const columns = {
],
syncAlbumUser: ['album_user.albumsId as albumId', 'album_user.usersId as userId', 'album_user.role'],
syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'],
syncUser: ['id', 'name', 'email', 'avatarColor', 'deletedAt', 'updateId'],
stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'],
syncAssetExif: [
'asset_exif.assetId',

View file

@ -10,6 +10,7 @@ import {
MemoryType,
SyncEntityType,
SyncRequestType,
UserAvatarColor,
UserMetadataKey,
} from 'src/enum';
import { UserMetadata } from 'src/types';
@ -58,9 +59,25 @@ export class SyncUserV1 {
id!: string;
name!: string;
email!: string;
@ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', nullable: true })
avatarColor!: UserAvatarColor | null;
deletedAt!: Date | null;
}
@ExtraModel()
export class SyncAuthUserV1 extends SyncUserV1 {
isAdmin!: boolean;
pinCode!: string | null;
oauthId!: string;
storageLabel!: string | null;
@ApiProperty({ type: 'integer' })
quotaSizeInBytes!: number | null;
@ApiProperty({ type: 'integer' })
quotaUsageInBytes!: number;
hasProfileImage!: boolean;
profileChangedAt!: Date;
}
@ExtraModel()
export class SyncUserDeleteV1 {
userId!: string;
@ -301,6 +318,7 @@ export class SyncAckV1 {}
export class SyncResetV1 {}
export type SyncItem = {
[SyncEntityType.AuthUserV1]: SyncAuthUserV1;
[SyncEntityType.UserV1]: SyncUserV1;
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
[SyncEntityType.PartnerV1]: SyncPartnerV1;

View file

@ -559,6 +559,7 @@ export enum SyncRequestType {
AlbumAssetExifsV1 = 'AlbumAssetExifsV1',
AssetsV1 = 'AssetsV1',
AssetExifsV1 = 'AssetExifsV1',
AuthUsersV1 = 'AuthUsersV1',
MemoriesV1 = 'MemoriesV1',
MemoryToAssetsV1 = 'MemoryToAssetsV1',
PartnersV1 = 'PartnersV1',
@ -573,6 +574,8 @@ export enum SyncRequestType {
}
export enum SyncEntityType {
AuthUserV1 = 'AuthUserV1',
UserV1 = 'UserV1',
UserDeleteV1 = 'UserDeleteV1',

View file

@ -444,6 +444,29 @@ where
order by
"asset_face"."updateId" asc
-- SyncRepository.authUser.getUpserts
select
"id",
"name",
"email",
"avatarColor",
"deletedAt",
"updateId",
"isAdmin",
"pinCode",
"oauthId",
"storageLabel",
"quotaSizeInBytes",
"quotaUsageInBytes",
"profileImagePath",
"profileChangedAt"
from
"user"
where
"updatedAt" < now() - interval '1 millisecond'
order by
"updateId" asc
-- SyncRepository.memory.getDeletes
select
"id",
@ -871,6 +894,7 @@ select
"id",
"name",
"email",
"avatarColor",
"deletedAt",
"updateId"
from

View file

@ -43,6 +43,7 @@ export class SyncRepository {
asset: AssetSync;
assetExif: AssetExifSync;
assetFace: AssetFaceSync;
authUser: AuthUserSync;
memory: MemorySync;
memoryToAsset: MemoryToAssetSync;
partner: PartnerSync;
@ -63,6 +64,7 @@ export class SyncRepository {
this.asset = new AssetSync(this.db);
this.assetExif = new AssetExifSync(this.db);
this.assetFace = new AssetFaceSync(this.db);
this.authUser = new AuthUserSync(this.db);
this.memory = new MemorySync(this.db);
this.memoryToAsset = new MemoryToAssetSync(this.db);
this.partner = new PartnerSync(this.db);
@ -367,6 +369,27 @@ class AssetSync extends BaseSync {
}
}
class AuthUserSync extends BaseSync {
@GenerateSql({ params: [], stream: true })
getUpserts(ack?: SyncAck) {
return this.db
.selectFrom('user')
.select(columns.syncUser)
.select([
'isAdmin',
'pinCode',
'oauthId',
'storageLabel',
'quotaSizeInBytes',
'quotaUsageInBytes',
'profileImagePath',
'profileChangedAt',
])
.$call(this.upsertTableFilters(ack))
.stream();
}
}
class PersonSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
@ -693,11 +716,7 @@ class UserSync extends BaseSync {
@GenerateSql({ params: [], stream: true })
getUpserts(ack?: SyncAck) {
return this.db
.selectFrom('user')
.select(['id', 'name', 'email', 'deletedAt', 'updateId'])
.$call(this.upsertTableFilters(ack))
.stream();
return this.db.selectFrom('user').select(columns.syncUser).$call(this.upsertTableFilters(ack)).stream();
}
}

View file

@ -54,6 +54,7 @@ const sendEntityBackfillCompleteAck = (response: Writable, ackType: SyncEntityTy
const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] };
export const SYNC_TYPES_ORDER = [
SyncRequestType.AuthUsersV1,
SyncRequestType.UsersV1,
SyncRequestType.PartnersV1,
SyncRequestType.AssetsV1,
@ -140,6 +141,7 @@ export class SyncService extends BaseService {
const checkpointMap: CheckpointMap = Object.fromEntries(checkpoints.map(({ type, ack }) => [type, fromAck(ack)]));
const handlers: Record<SyncRequestType, () => Promise<void>> = {
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(response, checkpointMap),
[SyncRequestType.UsersV1]: () => this.syncUsersV1(response, checkpointMap),
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(response, checkpointMap, auth),
[SyncRequestType.AssetsV1]: () => this.syncAssetsV1(response, checkpointMap, auth),
@ -169,6 +171,14 @@ export class SyncService extends BaseService {
response.end();
}
private async syncAuthUsersV1(response: Writable, checkpointMap: CheckpointMap) {
const upsertType = SyncEntityType.AuthUserV1;
const upserts = this.syncRepository.authUser.getUpserts(checkpointMap[upsertType]);
for await (const { updateId, profileImagePath, ...data } of upserts) {
send(response, { type: upsertType, ids: [updateId], data: { ...data, hasProfileImage: !!profileImagePath } });
}
}
private async syncUsersV1(response: Writable, checkpointMap: CheckpointMap) {
const deleteType = SyncEntityType.UserDeleteV1;
const deletes = this.syncRepository.user.getDeletes(checkpointMap[deleteType]);