chore: transition to nullable user.oauthId (#31560)
This commit is contained in:
@@ -4,10 +4,13 @@ enum ServerCapability {
|
|||||||
// Feature Support
|
// Feature Support
|
||||||
cloudIdMetadata(SemVer(major: 2, minor: 4, patch: 0)),
|
cloudIdMetadata(SemVer(major: 2, minor: 4, patch: 0)),
|
||||||
bulkCloudIdMetadata(SemVer(major: 2, minor: 5, patch: 0)),
|
bulkCloudIdMetadata(SemVer(major: 2, minor: 5, patch: 0)),
|
||||||
assetEdits(SemVer(major: 2, minor: 6, patch: 0)),
|
|
||||||
assetFacesV2(SemVer(major: 2, minor: 6, patch: 0)),
|
// Sync
|
||||||
syncV2(SemVer(major: 3, minor: 0, patch: 0)),
|
syncV2(SemVer(major: 3, minor: 0, patch: 0)),
|
||||||
assetOcr(SemVer(major: 3, minor: 0, patch: 0)),
|
syncAssetEditsV1(SemVer(major: 2, minor: 6, patch: 0)),
|
||||||
|
syncAssetFacesV2(SemVer(major: 2, minor: 6, patch: 0)),
|
||||||
|
syncAssetOcrV1(SemVer(major: 3, minor: 0, patch: 0)),
|
||||||
|
syncAuthUsersV2(SemVer(major: 3, minor: 3, patch: 0)),
|
||||||
|
|
||||||
// Migrations
|
// Migrations
|
||||||
assetPayloadChange20260128(SemVer(major: 2, minor: 5, patch: 0)),
|
assetPayloadChange20260128(SemVer(major: 2, minor: 5, patch: 0)),
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ class SyncStreamService {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case SyncEntityType.authUserV1:
|
case SyncEntityType.authUserV1:
|
||||||
return _syncStreamRepository.updateAuthUsersV1(data.cast());
|
return _syncStreamRepository.updateAuthUsersV1(data.cast());
|
||||||
|
case SyncEntityType.authUserV2:
|
||||||
|
return _syncStreamRepository.updateAuthUsersV2(data.cast());
|
||||||
case SyncEntityType.userV1:
|
case SyncEntityType.userV1:
|
||||||
return _syncStreamRepository.updateUsersV1(data.cast());
|
return _syncStreamRepository.updateUsersV1(data.cast());
|
||||||
case SyncEntityType.userDeleteV1:
|
case SyncEntityType.userDeleteV1:
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ class SyncApiRepository {
|
|||||||
request.body = jsonEncode(
|
request.body = jsonEncode(
|
||||||
SyncStreamDto(
|
SyncStreamDto(
|
||||||
types: [
|
types: [
|
||||||
SyncRequestType.authUsersV1,
|
serverVersion.supports(.syncAuthUsersV2) ? SyncRequestType.authUsersV2 : SyncRequestType.authUsersV1,
|
||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
serverVersion.supports(.syncV2) ? SyncRequestType.assetsV2 : SyncRequestType.assetsV1,
|
serverVersion.supports(.syncV2) ? SyncRequestType.assetsV2 : SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
if (serverVersion.supports(.assetEdits)) SyncRequestType.assetEditsV1,
|
if (serverVersion.supports(.syncAssetEditsV1)) SyncRequestType.assetEditsV1,
|
||||||
SyncRequestType.assetMetadataV1,
|
SyncRequestType.assetMetadataV1,
|
||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
serverVersion.supports(.syncV2) ? SyncRequestType.partnerAssetsV2 : SyncRequestType.partnerAssetsV1,
|
serverVersion.supports(.syncV2) ? SyncRequestType.partnerAssetsV2 : SyncRequestType.partnerAssetsV1,
|
||||||
@@ -63,8 +63,8 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.partnerStacksV1,
|
SyncRequestType.partnerStacksV1,
|
||||||
SyncRequestType.userMetadataV1,
|
SyncRequestType.userMetadataV1,
|
||||||
SyncRequestType.peopleV1,
|
SyncRequestType.peopleV1,
|
||||||
serverVersion.supports(.assetFacesV2) ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1,
|
serverVersion.supports(.syncAssetFacesV2) ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1,
|
||||||
if (serverVersion.supports(.assetOcr)) SyncRequestType.assetOcrV1,
|
if (serverVersion.supports(.syncAssetOcrV1)) SyncRequestType.assetOcrV1,
|
||||||
],
|
],
|
||||||
).toJson(),
|
).toJson(),
|
||||||
);
|
);
|
||||||
@@ -140,6 +140,7 @@ class SyncApiRepository {
|
|||||||
|
|
||||||
const _kResponseMap = <SyncEntityType, Function(Object)>{
|
const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||||
SyncEntityType.authUserV1: SyncAuthUserV1.fromJson,
|
SyncEntityType.authUserV1: SyncAuthUserV1.fromJson,
|
||||||
|
SyncEntityType.authUserV2: SyncAuthUserV2.fromJson,
|
||||||
SyncEntityType.userV1: SyncUserV1.fromJson,
|
SyncEntityType.userV1: SyncUserV1.fromJson,
|
||||||
SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
|
SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||||
|
|||||||
@@ -116,6 +116,35 @@ class SyncStreamRepository extends DatabaseAccessor<Drift> with $SyncStreamRepos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAuthUsersV2(Iterable<SyncAuthUserV2> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final user in data) {
|
||||||
|
final companion = AuthUserEntityCompanion(
|
||||||
|
name: Value(user.name),
|
||||||
|
email: Value(user.email),
|
||||||
|
hasProfileImage: Value(user.hasProfileImage),
|
||||||
|
profileChangedAt: Value(user.profileChangedAt),
|
||||||
|
avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary),
|
||||||
|
isAdmin: Value(user.isAdmin),
|
||||||
|
pinCode: Value(user.pinCode),
|
||||||
|
quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0),
|
||||||
|
quotaUsageInBytes: Value(user.quotaUsageInBytes),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.authUserEntity,
|
||||||
|
companion.copyWith(id: Value(user.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: SyncAuthUserV2', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/utils/error_handler.dart';
|
import 'package:immich_mobile/utils/error_handler.dart';
|
||||||
|
|
||||||
final _stateProvider = Provider.family.autoDispose<RemoteAsset?, ActionSource>((ref, source) {
|
final _stateProvider = Provider.family.autoDispose<RemoteAsset?, ActionSource>((ref, source) {
|
||||||
final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion.supports(.assetEdits)));
|
final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion.supports(.syncAssetEditsV1)));
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30710,6 +30710,98 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SyncAuthUserV2": {
|
||||||
|
"properties": {
|
||||||
|
"avatarColor": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UserAvatarColor"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"deletedAt": {
|
||||||
|
"description": "User deleted at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"description": "User email",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hasProfileImage": {
|
||||||
|
"description": "User has profile image",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "User ID",
|
||||||
|
"format": "uuid",
|
||||||
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isAdmin": {
|
||||||
|
"description": "User is admin",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "User name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"oauthId": {
|
||||||
|
"description": "User OAuth ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pinCode": {
|
||||||
|
"description": "User pin code",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"profileChangedAt": {
|
||||||
|
"description": "User profile changed at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"quotaSizeInBytes": {
|
||||||
|
"description": "Quota size in bytes",
|
||||||
|
"maximum": 9007199254740991,
|
||||||
|
"minimum": -9007199254740991,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quotaUsageInBytes": {
|
||||||
|
"description": "Quota usage in bytes",
|
||||||
|
"maximum": 9007199254740991,
|
||||||
|
"minimum": -9007199254740991,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"storageLabel": {
|
||||||
|
"description": "User storage label",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"deletedAt",
|
||||||
|
"email",
|
||||||
|
"hasProfileImage",
|
||||||
|
"id",
|
||||||
|
"isAdmin",
|
||||||
|
"name",
|
||||||
|
"oauthId",
|
||||||
|
"pinCode",
|
||||||
|
"profileChangedAt",
|
||||||
|
"quotaSizeInBytes",
|
||||||
|
"quotaUsageInBytes",
|
||||||
|
"storageLabel"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncCompleteV1": {
|
"SyncCompleteV1": {
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -30718,6 +30810,7 @@
|
|||||||
"description": "Sync entity type",
|
"description": "Sync entity type",
|
||||||
"enum": [
|
"enum": [
|
||||||
"AuthUserV1",
|
"AuthUserV1",
|
||||||
|
"AuthUserV2",
|
||||||
"UserV1",
|
"UserV1",
|
||||||
"UserDeleteV1",
|
"UserDeleteV1",
|
||||||
"AssetV1",
|
"AssetV1",
|
||||||
@@ -31082,6 +31175,7 @@
|
|||||||
"AssetMetadataV1",
|
"AssetMetadataV1",
|
||||||
"AssetOcrV1",
|
"AssetOcrV1",
|
||||||
"AuthUsersV1",
|
"AuthUsersV1",
|
||||||
|
"AuthUsersV2",
|
||||||
"MemoriesV1",
|
"MemoriesV1",
|
||||||
"MemoryToAssetsV1",
|
"MemoryToAssetsV1",
|
||||||
"PartnersV1",
|
"PartnersV1",
|
||||||
|
|||||||
@@ -3594,6 +3594,33 @@ export type SyncAuthUserV1 = {
|
|||||||
/** User storage label */
|
/** User storage label */
|
||||||
storageLabel: string | null;
|
storageLabel: string | null;
|
||||||
};
|
};
|
||||||
|
export type SyncAuthUserV2 = {
|
||||||
|
avatarColor?: (UserAvatarColor) | null;
|
||||||
|
/** User deleted at */
|
||||||
|
deletedAt: string | null;
|
||||||
|
/** User email */
|
||||||
|
email: string;
|
||||||
|
/** User has profile image */
|
||||||
|
hasProfileImage: boolean;
|
||||||
|
/** User ID */
|
||||||
|
id: string;
|
||||||
|
/** User is admin */
|
||||||
|
isAdmin: boolean;
|
||||||
|
/** User name */
|
||||||
|
name: string;
|
||||||
|
/** User OAuth ID */
|
||||||
|
oauthId: string | null;
|
||||||
|
/** User pin code */
|
||||||
|
pinCode: string | null;
|
||||||
|
/** User profile changed at */
|
||||||
|
profileChangedAt: string;
|
||||||
|
/** Quota size in bytes */
|
||||||
|
quotaSizeInBytes: number | null;
|
||||||
|
/** Quota usage in bytes */
|
||||||
|
quotaUsageInBytes: number;
|
||||||
|
/** User storage label */
|
||||||
|
storageLabel: string | null;
|
||||||
|
};
|
||||||
export type SyncCompleteV1 = {};
|
export type SyncCompleteV1 = {};
|
||||||
export type SyncMemoryAssetDeleteV1 = {
|
export type SyncMemoryAssetDeleteV1 = {
|
||||||
/** Asset ID */
|
/** Asset ID */
|
||||||
@@ -8306,6 +8333,7 @@ export enum AssetIdErrorReason {
|
|||||||
}
|
}
|
||||||
export enum SyncEntityType {
|
export enum SyncEntityType {
|
||||||
AuthUserV1 = "AuthUserV1",
|
AuthUserV1 = "AuthUserV1",
|
||||||
|
AuthUserV2 = "AuthUserV2",
|
||||||
UserV1 = "UserV1",
|
UserV1 = "UserV1",
|
||||||
UserDeleteV1 = "UserDeleteV1",
|
UserDeleteV1 = "UserDeleteV1",
|
||||||
AssetV1 = "AssetV1",
|
AssetV1 = "AssetV1",
|
||||||
@@ -8380,6 +8408,7 @@ export enum SyncRequestType {
|
|||||||
AssetMetadataV1 = "AssetMetadataV1",
|
AssetMetadataV1 = "AssetMetadataV1",
|
||||||
AssetOcrV1 = "AssetOcrV1",
|
AssetOcrV1 = "AssetOcrV1",
|
||||||
AuthUsersV1 = "AuthUsersV1",
|
AuthUsersV1 = "AuthUsersV1",
|
||||||
|
AuthUsersV2 = "AuthUsersV2",
|
||||||
MemoriesV1 = "MemoriesV1",
|
MemoriesV1 = "MemoriesV1",
|
||||||
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
||||||
PartnersV1 = "PartnersV1",
|
PartnersV1 = "PartnersV1",
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export type UserAdmin = User & {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
deletedAt: Date | null;
|
deletedAt: Date | null;
|
||||||
oauthId: string;
|
oauthId: string | null;
|
||||||
quotaSizeInBytes: number | null;
|
quotaSizeInBytes: number | null;
|
||||||
quotaUsageInBytes: number;
|
quotaUsageInBytes: number;
|
||||||
status: UserStatus;
|
status: UserStatus;
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ const SyncAuthUserV1Schema = SyncUserV1Schema.merge(
|
|||||||
}),
|
}),
|
||||||
).meta({ id: 'SyncAuthUserV1' });
|
).meta({ id: 'SyncAuthUserV1' });
|
||||||
|
|
||||||
|
const SyncAuthUserV2Schema = SyncAuthUserV1Schema.extend({
|
||||||
|
oauthId: z.string().nullable().describe('User OAuth ID'),
|
||||||
|
}).meta({ id: 'SyncAuthUserV2' });
|
||||||
|
|
||||||
const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
||||||
|
|
||||||
const SyncPartnerV1Schema = z
|
const SyncPartnerV1Schema = z
|
||||||
@@ -112,6 +116,8 @@ class SyncUserV1 extends createZodDto(SyncUserV1Schema) {}
|
|||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncAuthUserV1 extends createZodDto(SyncAuthUserV1Schema) {}
|
class SyncAuthUserV1 extends createZodDto(SyncAuthUserV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
|
class SyncAuthUserV2 extends createZodDto(SyncAuthUserV2Schema) {}
|
||||||
|
@ExtraModel()
|
||||||
class SyncUserDeleteV1 extends createZodDto(SyncUserDeleteV1Schema) {}
|
class SyncUserDeleteV1 extends createZodDto(SyncUserDeleteV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncPartnerV1 extends createZodDto(SyncPartnerV1Schema) {}
|
class SyncPartnerV1 extends createZodDto(SyncPartnerV1Schema) {}
|
||||||
@@ -463,6 +469,7 @@ class SyncCompleteV1 extends createZodDto(SyncCompleteV1Schema) {}
|
|||||||
|
|
||||||
export type SyncItem = {
|
export type SyncItem = {
|
||||||
[SyncEntityType.AuthUserV1]: SyncAuthUserV1;
|
[SyncEntityType.AuthUserV1]: SyncAuthUserV1;
|
||||||
|
[SyncEntityType.AuthUserV2]: SyncAuthUserV2;
|
||||||
[SyncEntityType.UserV1]: SyncUserV1;
|
[SyncEntityType.UserV1]: SyncUserV1;
|
||||||
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
||||||
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto {
|
|||||||
createdAt: entity.createdAt,
|
createdAt: entity.createdAt,
|
||||||
deletedAt: entity.deletedAt,
|
deletedAt: entity.deletedAt,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
oauthId: entity.oauthId,
|
// TODO(v4): remove the mapping and make `oauthId` nullable
|
||||||
|
oauthId: entity.oauthId ?? '',
|
||||||
quotaSizeInBytes: entity.quotaSizeInBytes,
|
quotaSizeInBytes: entity.quotaSizeInBytes,
|
||||||
quotaUsageInBytes: entity.quotaUsageInBytes,
|
quotaUsageInBytes: entity.quotaUsageInBytes,
|
||||||
status: entity.status,
|
status: entity.status,
|
||||||
|
|||||||
@@ -1024,6 +1024,7 @@ export enum SyncRequestType {
|
|||||||
AssetMetadataV1 = 'AssetMetadataV1',
|
AssetMetadataV1 = 'AssetMetadataV1',
|
||||||
AssetOcrV1 = 'AssetOcrV1',
|
AssetOcrV1 = 'AssetOcrV1',
|
||||||
AuthUsersV1 = 'AuthUsersV1',
|
AuthUsersV1 = 'AuthUsersV1',
|
||||||
|
AuthUsersV2 = 'AuthUsersV2',
|
||||||
MemoriesV1 = 'MemoriesV1',
|
MemoriesV1 = 'MemoriesV1',
|
||||||
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
||||||
PartnersV1 = 'PartnersV1',
|
PartnersV1 = 'PartnersV1',
|
||||||
@@ -1048,6 +1049,7 @@ export const SyncRequestTypeSchema = z
|
|||||||
|
|
||||||
export enum SyncEntityType {
|
export enum SyncEntityType {
|
||||||
AuthUserV1 = 'AuthUserV1',
|
AuthUserV1 = 'AuthUserV1',
|
||||||
|
AuthUserV2 = 'AuthUserV2',
|
||||||
|
|
||||||
UserV1 = 'UserV1',
|
UserV1 = 'UserV1',
|
||||||
UserDeleteV1 = 'UserDeleteV1',
|
UserDeleteV1 = 'UserDeleteV1',
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ type UserEvent = {
|
|||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
shouldChangePassword: boolean;
|
shouldChangePassword: boolean;
|
||||||
avatarColor: UserAvatarColor | null;
|
avatarColor: UserAvatarColor | null;
|
||||||
oauthId: string;
|
oauthId: string | null;
|
||||||
storageLabel: string | null;
|
storageLabel: string | null;
|
||||||
quotaSizeInBytes: number | null;
|
quotaSizeInBytes: number | null;
|
||||||
quotaUsageInBytes: number;
|
quotaUsageInBytes: number;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "user" ALTER COLUMN "oauthId" DROP NOT NULL;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "user" ALTER COLUMN "oauthId" SET DEFAULT NULL;`.execute(db);
|
||||||
|
await sql`UPDATE "user" SET "oauthId" = NULL WHERE "oauthId" = '';`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`UPDATE "user" SET "oauthId" = '' WHERE "oauthId" IS NULL;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "user" ALTER COLUMN "oauthId" SET DEFAULT '';`.execute(db);
|
||||||
|
await sql`ALTER TABLE "user" ALTER COLUMN "oauthId" SET NOT NULL;`.execute(db);
|
||||||
|
}
|
||||||
@@ -94,3 +94,4 @@
|
|||||||
1786972746372-AssetOcrSyncReset
|
1786972746372-AssetOcrSyncReset
|
||||||
1787148183729-ClusterGroups
|
1787148183729-ClusterGroups
|
||||||
1787148183730-DeleteMismatchedMemoryAssets
|
1787148183730-DeleteMismatchedMemoryAssets
|
||||||
|
1789419229196-ConvertUserOAuthIdEmptyStringToNull
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export class UserTable {
|
|||||||
@DeleteDateColumn()
|
@DeleteDateColumn()
|
||||||
deletedAt!: Timestamp | null;
|
deletedAt!: Timestamp | null;
|
||||||
|
|
||||||
@Column({ default: '' })
|
@Column({ nullable: true })
|
||||||
oauthId!: Generated<string>;
|
oauthId!: string | null;
|
||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt!: Generated<Timestamp>;
|
updatedAt!: Generated<Timestamp>;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { BaseService } from 'src/services/base.service.js';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthAdminService extends BaseService {
|
export class AuthAdminService extends BaseService {
|
||||||
async unlinkAll(_auth: AuthDto) {
|
async unlinkAll(_auth: AuthDto) {
|
||||||
// TODO replace '' with null
|
await this.userRepository.updateAll({ oauthId: null });
|
||||||
await this.userRepository.updateAll({ oauthId: '' });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1057,12 +1057,13 @@ describe(AuthService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync the profile picture if the user already has one', async () => {
|
it('should not sync the profile picture if the user already has one', async () => {
|
||||||
const user = UserFactory.create({ oauthId: 'oauth-id', profileImagePath: 'not-empty' });
|
const oauthId = 'oauth-id';
|
||||||
|
const user = UserFactory.create({ oauthId, profileImagePath: 'not-empty' });
|
||||||
|
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||||
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
||||||
profile: OAuthProfileFactory.create({
|
profile: OAuthProfileFactory.create({
|
||||||
sub: user.oauthId,
|
sub: oauthId,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
picture: 'https://auth.immich.cloud/profiles/1.jpg',
|
picture: 'https://auth.immich.cloud/profiles/1.jpg',
|
||||||
}),
|
}),
|
||||||
@@ -1181,11 +1182,12 @@ describe(AuthService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should promote an existing user to admin if the role claim contains admin on login', async () => {
|
it('should promote an existing user to admin if the role claim contains admin on login', async () => {
|
||||||
const user = UserFactory.create({ isAdmin: false, oauthId: 'oauth-id' });
|
const oauthId = 'oauth-id';
|
||||||
|
const user = UserFactory.create({ isAdmin: false, oauthId });
|
||||||
|
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||||
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
||||||
profile: OAuthProfileFactory.create({ sub: user.oauthId, immich_role: 'admin' }),
|
profile: OAuthProfileFactory.create({ sub: oauthId, immich_role: 'admin' }),
|
||||||
});
|
});
|
||||||
mocks.user.getByOAuthId.mockResolvedValue(user);
|
mocks.user.getByOAuthId.mockResolvedValue(user);
|
||||||
mocks.user.update.mockResolvedValue({ ...user, isAdmin: true });
|
mocks.user.update.mockResolvedValue({ ...user, isAdmin: true });
|
||||||
@@ -1201,11 +1203,12 @@ describe(AuthService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should demote an existing admin if the role claim only contains user on login', async () => {
|
it('should demote an existing admin if the role claim only contains user on login', async () => {
|
||||||
const user = UserFactory.create({ isAdmin: true, oauthId: 'oauth-id' });
|
const oauthId = 'oauth-id';
|
||||||
|
const user = UserFactory.create({ isAdmin: true, oauthId });
|
||||||
|
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||||
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
||||||
profile: OAuthProfileFactory.create({ sub: user.oauthId, immich_role: ['user'] }),
|
profile: OAuthProfileFactory.create({ sub: oauthId, immich_role: ['user'] }),
|
||||||
});
|
});
|
||||||
mocks.user.getByOAuthId.mockResolvedValue(user);
|
mocks.user.getByOAuthId.mockResolvedValue(user);
|
||||||
mocks.user.update.mockResolvedValue({ ...user, isAdmin: false });
|
mocks.user.update.mockResolvedValue({ ...user, isAdmin: false });
|
||||||
@@ -1221,11 +1224,12 @@ describe(AuthService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not change isAdmin for an existing user if the role claim is blank', async () => {
|
it('should not change isAdmin for an existing user if the role claim is blank', async () => {
|
||||||
const user = UserFactory.create({ isAdmin: true, oauthId: 'oauth-id' });
|
const oauthId = 'oauth-id';
|
||||||
|
const user = UserFactory.create({ isAdmin: true, oauthId });
|
||||||
|
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||||
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
mocks.oauth.getProfileAndOAuthSid.mockResolvedValue({
|
||||||
profile: OAuthProfileFactory.create({ sub: user.oauthId }),
|
profile: OAuthProfileFactory.create({ sub: oauthId }),
|
||||||
});
|
});
|
||||||
mocks.user.getByOAuthId.mockResolvedValue(user);
|
mocks.user.getByOAuthId.mockResolvedValue(user);
|
||||||
mocks.session.create.mockResolvedValue(SessionFactory.create());
|
mocks.session.create.mockResolvedValue(SessionFactory.create());
|
||||||
@@ -1334,7 +1338,7 @@ describe(AuthService.name, () => {
|
|||||||
|
|
||||||
await sut.unlink(auth);
|
await sut.unlink(auth);
|
||||||
|
|
||||||
expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: '' });
|
expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should unlink an account and remove the OAuth data from the session', async () => {
|
it('should unlink an account and remove the OAuth data from the session', async () => {
|
||||||
@@ -1349,7 +1353,7 @@ describe(AuthService.name, () => {
|
|||||||
await sut.unlink(auth);
|
await sut.unlink(auth);
|
||||||
|
|
||||||
expect(mocks.session.update).toHaveBeenCalledWith(session.id, { oauthSid: null, oauthBearerToken: null });
|
expect(mocks.session.update).toHaveBeenCalledWith(session.id, { oauthSid: null, oauthBearerToken: null });
|
||||||
expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: '' });
|
expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: null });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ export class AuthService extends BaseService {
|
|||||||
await this.sessionRepository.update(auth.session.id, { oauthSid: null, oauthBearerToken: null });
|
await this.sessionRepository.update(auth.session.id, { oauthSid: null, oauthBearerToken: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.userRepository.update(auth.user.id, { oauthId: '' });
|
const user = await this.userRepository.update(auth.user.id, { oauthId: null });
|
||||||
return mapUserAdmin(user);
|
return mapUserAdmin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const sendEntityBackfillCompleteAck = async (response: Writable, ackType: SyncEn
|
|||||||
|
|
||||||
export const SYNC_TYPES_ORDER = [
|
export const SYNC_TYPES_ORDER = [
|
||||||
SyncRequestType.AuthUsersV1,
|
SyncRequestType.AuthUsersV1,
|
||||||
|
SyncRequestType.AuthUsersV2,
|
||||||
SyncRequestType.UsersV1,
|
SyncRequestType.UsersV1,
|
||||||
SyncRequestType.PartnersV1,
|
SyncRequestType.PartnersV1,
|
||||||
SyncRequestType.AssetsV1,
|
SyncRequestType.AssetsV1,
|
||||||
@@ -190,6 +191,7 @@ export class SyncService extends BaseService {
|
|||||||
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(),
|
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(),
|
||||||
|
|
||||||
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
|
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
|
||||||
|
[SyncRequestType.AuthUsersV2]: () => this.syncAuthUsersV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
|
[SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
|
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap),
|
[SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap),
|
||||||
@@ -266,6 +268,18 @@ export class SyncService extends BaseService {
|
|||||||
private async syncAuthUsersV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
private async syncAuthUsersV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
||||||
const upsertType = SyncEntityType.AuthUserV1;
|
const upsertType = SyncEntityType.AuthUserV1;
|
||||||
const upserts = this.syncRepository.authUser.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
const upserts = this.syncRepository.authUser.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
|
for await (const { updateId, profileImagePath, ...data } of upserts) {
|
||||||
|
await send(response, {
|
||||||
|
type: upsertType,
|
||||||
|
ids: [updateId],
|
||||||
|
data: { ...data, oauthId: data.oauthId ?? '', hasProfileImage: !!profileImagePath },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAuthUsersV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
||||||
|
const upsertType = SyncEntityType.AuthUserV2;
|
||||||
|
const upserts = this.syncRepository.authUser.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
for await (const { updateId, profileImagePath, ...data } of upserts) {
|
for await (const { updateId, profileImagePath, ...data } of upserts) {
|
||||||
await send(response, {
|
await send(response, {
|
||||||
type: upsertType,
|
type: upsertType,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class UserFactory {
|
|||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
avatarColor: null,
|
avatarColor: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
oauthId: '',
|
oauthId: null,
|
||||||
updatedAt: newDate(),
|
updatedAt: newDate(),
|
||||||
storageLabel: null,
|
storageLabel: null,
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
|
|||||||
Vendored
+2
-2
@@ -11,7 +11,7 @@ export const userStub = {
|
|||||||
name: 'admin_name',
|
name: 'admin_name',
|
||||||
id: 'admin_id',
|
id: 'admin_id',
|
||||||
storageLabel: 'admin',
|
storageLabel: 'admin',
|
||||||
oauthId: '',
|
oauthId: null,
|
||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
avatarColor: null,
|
avatarColor: null,
|
||||||
profileImagePath: '',
|
profileImagePath: '',
|
||||||
@@ -29,7 +29,7 @@ export const userStub = {
|
|||||||
profileChangedAt: new Date('2021-01-01'),
|
profileChangedAt: new Date('2021-01-01'),
|
||||||
name: 'immich_name',
|
name: 'immich_name',
|
||||||
storageLabel: null,
|
storageLabel: null,
|
||||||
oauthId: '',
|
oauthId: null,
|
||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
avatarColor: null,
|
avatarColor: null,
|
||||||
profileImagePath: '',
|
profileImagePath: '',
|
||||||
|
|||||||
@@ -761,7 +761,7 @@ const userInsert = (user: Partial<Insertable<UserTable>> & { clusterGroupId: str
|
|||||||
shouldChangePassword: true,
|
shouldChangePassword: true,
|
||||||
storageLabel: null,
|
storageLabel: null,
|
||||||
pinCode: null,
|
pinCode: null,
|
||||||
oauthId: '',
|
oauthId: null,
|
||||||
avatarColor: null,
|
avatarColor: null,
|
||||||
quotaSizeInBytes: null,
|
quotaSizeInBytes: null,
|
||||||
quotaUsageInBytes: 0,
|
quotaUsageInBytes: 0,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe(AuthAdminService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||||
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
||||||
expect.objectContaining({ oauthId: '' }),
|
expect.objectContaining({ oauthId: null }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ describe(AuthAdminService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||||
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
||||||
expect.objectContaining({ oauthId: '' }),
|
expect.objectContaining({ oauthId: null }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,10 +56,10 @@ describe(AuthAdminService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||||
await expect(userRepo.get(user1.id, { withDeleted: true })).resolves.toEqual(
|
await expect(userRepo.get(user1.id, { withDeleted: true })).resolves.toEqual(
|
||||||
expect.objectContaining({ oauthId: '' }),
|
expect.objectContaining({ oauthId: null }),
|
||||||
);
|
);
|
||||||
await expect(userRepo.get(user2.id, { withDeleted: true })).resolves.toEqual(
|
await expect(userRepo.get(user2.id, { withDeleted: true })).resolves.toEqual(
|
||||||
expect.objectContaining({ oauthId: '' }),
|
expect.objectContaining({ oauthId: null }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ beforeAll(async () => {
|
|||||||
defaultDatabase = await getKyselyDB();
|
defaultDatabase = await getKyselyDB();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SyncEntityType.AuthUserV1, () => {
|
describe(SyncEntityType.AuthUserV2, () => {
|
||||||
it('should detect and sync the first user', async () => {
|
it('should detect and sync the first user', async () => {
|
||||||
const { auth, user, ctx } = await setup(await getKyselyDB());
|
const { auth, user, ctx } = await setup(await getKyselyDB());
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -40,13 +40,13 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
quotaUsageInBytes: user.quotaUsageInBytes,
|
quotaUsageInBytes: user.quotaUsageInBytes,
|
||||||
storageLabel: user.storageLabel,
|
storageLabel: user.storageLabel,
|
||||||
},
|
},
|
||||||
type: 'AuthUserV1',
|
type: 'AuthUserV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AuthUsersV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AuthUsersV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync a change and then another change to that same user', async () => {
|
it('should sync a change and then another change to that same user', async () => {
|
||||||
@@ -54,7 +54,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
|
|
||||||
const userRepo = ctx.get(UserRepository);
|
const userRepo = ctx.get(UserRepository);
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -62,7 +62,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
}),
|
}),
|
||||||
type: 'AuthUserV1',
|
type: 'AuthUserV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -71,7 +71,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
|
|
||||||
await userRepo.update(user.id, { isAdmin: true });
|
await userRepo.update(user.id, { isAdmin: true });
|
||||||
|
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV2]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -79,7 +79,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
}),
|
}),
|
||||||
type: 'AuthUserV1',
|
type: 'AuthUserV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -90,7 +90,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
|
|
||||||
await ctx.newUser();
|
await ctx.newUser();
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -98,7 +98,7 @@ describe(SyncEntityType.AuthUserV1, () => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
}),
|
}),
|
||||||
type: 'AuthUserV1',
|
type: 'AuthUserV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const userAdminFactory = (user: Partial<UserAdmin> = {}) => {
|
|||||||
createdAt = newDate(),
|
createdAt = newDate(),
|
||||||
updatedAt = newDate(),
|
updatedAt = newDate(),
|
||||||
deletedAt = null,
|
deletedAt = null,
|
||||||
oauthId = '',
|
oauthId = null,
|
||||||
quotaSizeInBytes = null,
|
quotaSizeInBytes = null,
|
||||||
quotaUsageInBytes = 0,
|
quotaUsageInBytes = 0,
|
||||||
status = UserStatus.Active,
|
status = UserStatus.Active,
|
||||||
|
|||||||
Reference in New Issue
Block a user