mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
parent
869839f642
commit
fe702ba6d7
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -425,6 +425,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAckDto](doc//SyncAckDto.md)
|
- [SyncAckDto](doc//SyncAckDto.md)
|
||||||
- [SyncAckSetDto](doc//SyncAckSetDto.md)
|
- [SyncAckSetDto](doc//SyncAckSetDto.md)
|
||||||
- [SyncEntityType](doc//SyncEntityType.md)
|
- [SyncEntityType](doc//SyncEntityType.md)
|
||||||
|
- [SyncPartnerDeleteV1](doc//SyncPartnerDeleteV1.md)
|
||||||
|
- [SyncPartnerV1](doc//SyncPartnerV1.md)
|
||||||
- [SyncRequestType](doc//SyncRequestType.md)
|
- [SyncRequestType](doc//SyncRequestType.md)
|
||||||
- [SyncStreamDto](doc//SyncStreamDto.md)
|
- [SyncStreamDto](doc//SyncStreamDto.md)
|
||||||
- [SyncUserDeleteV1](doc//SyncUserDeleteV1.md)
|
- [SyncUserDeleteV1](doc//SyncUserDeleteV1.md)
|
||||||
|
2
mobile/openapi/lib/api.dart
generated
2
mobile/openapi/lib/api.dart
generated
@ -232,6 +232,8 @@ part 'model/sync_ack_delete_dto.dart';
|
|||||||
part 'model/sync_ack_dto.dart';
|
part 'model/sync_ack_dto.dart';
|
||||||
part 'model/sync_ack_set_dto.dart';
|
part 'model/sync_ack_set_dto.dart';
|
||||||
part 'model/sync_entity_type.dart';
|
part 'model/sync_entity_type.dart';
|
||||||
|
part 'model/sync_partner_delete_v1.dart';
|
||||||
|
part 'model/sync_partner_v1.dart';
|
||||||
part 'model/sync_request_type.dart';
|
part 'model/sync_request_type.dart';
|
||||||
part 'model/sync_stream_dto.dart';
|
part 'model/sync_stream_dto.dart';
|
||||||
part 'model/sync_user_delete_v1.dart';
|
part 'model/sync_user_delete_v1.dart';
|
||||||
|
4
mobile/openapi/lib/api_client.dart
generated
4
mobile/openapi/lib/api_client.dart
generated
@ -520,6 +520,10 @@ class ApiClient {
|
|||||||
return SyncAckSetDto.fromJson(value);
|
return SyncAckSetDto.fromJson(value);
|
||||||
case 'SyncEntityType':
|
case 'SyncEntityType':
|
||||||
return SyncEntityTypeTypeTransformer().decode(value);
|
return SyncEntityTypeTypeTransformer().decode(value);
|
||||||
|
case 'SyncPartnerDeleteV1':
|
||||||
|
return SyncPartnerDeleteV1.fromJson(value);
|
||||||
|
case 'SyncPartnerV1':
|
||||||
|
return SyncPartnerV1.fromJson(value);
|
||||||
case 'SyncRequestType':
|
case 'SyncRequestType':
|
||||||
return SyncRequestTypeTypeTransformer().decode(value);
|
return SyncRequestTypeTypeTransformer().decode(value);
|
||||||
case 'SyncStreamDto':
|
case 'SyncStreamDto':
|
||||||
|
6
mobile/openapi/lib/model/sync_entity_type.dart
generated
6
mobile/openapi/lib/model/sync_entity_type.dart
generated
@ -25,11 +25,15 @@ class SyncEntityType {
|
|||||||
|
|
||||||
static const userV1 = SyncEntityType._(r'UserV1');
|
static const userV1 = SyncEntityType._(r'UserV1');
|
||||||
static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1');
|
static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1');
|
||||||
|
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
||||||
|
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
|
||||||
|
|
||||||
/// List of all possible values in this [enum][SyncEntityType].
|
/// List of all possible values in this [enum][SyncEntityType].
|
||||||
static const values = <SyncEntityType>[
|
static const values = <SyncEntityType>[
|
||||||
userV1,
|
userV1,
|
||||||
userDeleteV1,
|
userDeleteV1,
|
||||||
|
partnerV1,
|
||||||
|
partnerDeleteV1,
|
||||||
];
|
];
|
||||||
|
|
||||||
static SyncEntityType? fromJson(dynamic value) => SyncEntityTypeTypeTransformer().decode(value);
|
static SyncEntityType? fromJson(dynamic value) => SyncEntityTypeTypeTransformer().decode(value);
|
||||||
@ -70,6 +74,8 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
switch (data) {
|
switch (data) {
|
||||||
case r'UserV1': return SyncEntityType.userV1;
|
case r'UserV1': return SyncEntityType.userV1;
|
||||||
case r'UserDeleteV1': return SyncEntityType.userDeleteV1;
|
case r'UserDeleteV1': return SyncEntityType.userDeleteV1;
|
||||||
|
case r'PartnerV1': return SyncEntityType.partnerV1;
|
||||||
|
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
107
mobile/openapi/lib/model/sync_partner_delete_v1.dart
generated
Normal file
107
mobile/openapi/lib/model/sync_partner_delete_v1.dart
generated
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SyncPartnerDeleteV1 {
|
||||||
|
/// Returns a new [SyncPartnerDeleteV1] instance.
|
||||||
|
SyncPartnerDeleteV1({
|
||||||
|
required this.sharedById,
|
||||||
|
required this.sharedWithId,
|
||||||
|
});
|
||||||
|
|
||||||
|
String sharedById;
|
||||||
|
|
||||||
|
String sharedWithId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncPartnerDeleteV1 &&
|
||||||
|
other.sharedById == sharedById &&
|
||||||
|
other.sharedWithId == sharedWithId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(sharedById.hashCode) +
|
||||||
|
(sharedWithId.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncPartnerDeleteV1[sharedById=$sharedById, sharedWithId=$sharedWithId]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'sharedById'] = this.sharedById;
|
||||||
|
json[r'sharedWithId'] = this.sharedWithId;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncPartnerDeleteV1] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncPartnerDeleteV1? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncPartnerDeleteV1");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncPartnerDeleteV1(
|
||||||
|
sharedById: mapValueOfType<String>(json, r'sharedById')!,
|
||||||
|
sharedWithId: mapValueOfType<String>(json, r'sharedWithId')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncPartnerDeleteV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncPartnerDeleteV1>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncPartnerDeleteV1.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncPartnerDeleteV1> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncPartnerDeleteV1>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncPartnerDeleteV1.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncPartnerDeleteV1-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncPartnerDeleteV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncPartnerDeleteV1>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncPartnerDeleteV1.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'sharedById',
|
||||||
|
'sharedWithId',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
115
mobile/openapi/lib/model/sync_partner_v1.dart
generated
Normal file
115
mobile/openapi/lib/model/sync_partner_v1.dart
generated
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SyncPartnerV1 {
|
||||||
|
/// Returns a new [SyncPartnerV1] instance.
|
||||||
|
SyncPartnerV1({
|
||||||
|
required this.inTimeline,
|
||||||
|
required this.sharedById,
|
||||||
|
required this.sharedWithId,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool inTimeline;
|
||||||
|
|
||||||
|
String sharedById;
|
||||||
|
|
||||||
|
String sharedWithId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncPartnerV1 &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
other.sharedById == sharedById &&
|
||||||
|
other.sharedWithId == sharedWithId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(inTimeline.hashCode) +
|
||||||
|
(sharedById.hashCode) +
|
||||||
|
(sharedWithId.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncPartnerV1[inTimeline=$inTimeline, sharedById=$sharedById, sharedWithId=$sharedWithId]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'inTimeline'] = this.inTimeline;
|
||||||
|
json[r'sharedById'] = this.sharedById;
|
||||||
|
json[r'sharedWithId'] = this.sharedWithId;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncPartnerV1] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncPartnerV1? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncPartnerV1");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncPartnerV1(
|
||||||
|
inTimeline: mapValueOfType<bool>(json, r'inTimeline')!,
|
||||||
|
sharedById: mapValueOfType<String>(json, r'sharedById')!,
|
||||||
|
sharedWithId: mapValueOfType<String>(json, r'sharedWithId')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncPartnerV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncPartnerV1>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncPartnerV1.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncPartnerV1> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncPartnerV1>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncPartnerV1.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncPartnerV1-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncPartnerV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncPartnerV1>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncPartnerV1.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'inTimeline',
|
||||||
|
'sharedById',
|
||||||
|
'sharedWithId',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
3
mobile/openapi/lib/model/sync_request_type.dart
generated
3
mobile/openapi/lib/model/sync_request_type.dart
generated
@ -24,10 +24,12 @@ class SyncRequestType {
|
|||||||
String toJson() => value;
|
String toJson() => value;
|
||||||
|
|
||||||
static const usersV1 = SyncRequestType._(r'UsersV1');
|
static const usersV1 = SyncRequestType._(r'UsersV1');
|
||||||
|
static const partnersV1 = SyncRequestType._(r'PartnersV1');
|
||||||
|
|
||||||
/// List of all possible values in this [enum][SyncRequestType].
|
/// List of all possible values in this [enum][SyncRequestType].
|
||||||
static const values = <SyncRequestType>[
|
static const values = <SyncRequestType>[
|
||||||
usersV1,
|
usersV1,
|
||||||
|
partnersV1,
|
||||||
];
|
];
|
||||||
|
|
||||||
static SyncRequestType? fromJson(dynamic value) => SyncRequestTypeTypeTransformer().decode(value);
|
static SyncRequestType? fromJson(dynamic value) => SyncRequestTypeTypeTransformer().decode(value);
|
||||||
@ -67,6 +69,7 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case r'UsersV1': return SyncRequestType.usersV1;
|
case r'UsersV1': return SyncRequestType.usersV1;
|
||||||
|
case r'PartnersV1': return SyncRequestType.partnersV1;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
@ -12052,13 +12052,50 @@
|
|||||||
"SyncEntityType": {
|
"SyncEntityType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"UserV1",
|
"UserV1",
|
||||||
"UserDeleteV1"
|
"UserDeleteV1",
|
||||||
|
"PartnerV1",
|
||||||
|
"PartnerDeleteV1"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"SyncPartnerDeleteV1": {
|
||||||
|
"properties": {
|
||||||
|
"sharedById": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sharedWithId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sharedById",
|
||||||
|
"sharedWithId"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SyncPartnerV1": {
|
||||||
|
"properties": {
|
||||||
|
"inTimeline": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sharedById": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sharedWithId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"inTimeline",
|
||||||
|
"sharedById",
|
||||||
|
"sharedWithId"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncRequestType": {
|
"SyncRequestType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"UsersV1"
|
"UsersV1",
|
||||||
|
"PartnersV1"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -3645,10 +3645,13 @@ export enum Error2 {
|
|||||||
}
|
}
|
||||||
export enum SyncEntityType {
|
export enum SyncEntityType {
|
||||||
UserV1 = "UserV1",
|
UserV1 = "UserV1",
|
||||||
UserDeleteV1 = "UserDeleteV1"
|
UserDeleteV1 = "UserDeleteV1",
|
||||||
|
PartnerV1 = "PartnerV1",
|
||||||
|
PartnerDeleteV1 = "PartnerDeleteV1"
|
||||||
}
|
}
|
||||||
export enum SyncRequestType {
|
export enum SyncRequestType {
|
||||||
UsersV1 = "UsersV1"
|
UsersV1 = "UsersV1",
|
||||||
|
PartnersV1 = "PartnersV1"
|
||||||
}
|
}
|
||||||
export enum TranscodeHWAccel {
|
export enum TranscodeHWAccel {
|
||||||
Nvenc = "nvenc",
|
Nvenc = "nvenc",
|
||||||
|
9
server/src/db.d.ts
vendored
9
server/src/db.d.ts
vendored
@ -272,6 +272,13 @@ export interface NaturalearthCountries {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PartnersAudit {
|
||||||
|
deletedAt: Generated<Timestamp>;
|
||||||
|
id: Generated<string>;
|
||||||
|
sharedById: string;
|
||||||
|
sharedWithId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Partners {
|
export interface Partners {
|
||||||
createdAt: Generated<Timestamp>;
|
createdAt: Generated<Timestamp>;
|
||||||
inTimeline: Generated<boolean>;
|
inTimeline: Generated<boolean>;
|
||||||
@ -316,7 +323,6 @@ export interface SessionSyncCheckpoints {
|
|||||||
updateId: Generated<string>;
|
updateId: Generated<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface SharedLinkAsset {
|
export interface SharedLinkAsset {
|
||||||
assetsId: string;
|
assetsId: string;
|
||||||
sharedLinksId: string;
|
sharedLinksId: string;
|
||||||
@ -462,6 +468,7 @@ export interface DB {
|
|||||||
migrations: Migrations;
|
migrations: Migrations;
|
||||||
move_history: MoveHistory;
|
move_history: MoveHistory;
|
||||||
naturalearth_countries: NaturalearthCountries;
|
naturalearth_countries: NaturalearthCountries;
|
||||||
|
partners_audit: PartnersAudit;
|
||||||
partners: Partners;
|
partners: Partners;
|
||||||
person: Person;
|
person: Person;
|
||||||
sessions: Sessions;
|
sessions: Sessions;
|
||||||
|
@ -45,15 +45,30 @@ export class SyncUserDeleteV1 {
|
|||||||
userId!: string;
|
userId!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SyncPartnerV1 {
|
||||||
|
sharedById!: string;
|
||||||
|
sharedWithId!: string;
|
||||||
|
inTimeline!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SyncPartnerDeleteV1 {
|
||||||
|
sharedById!: string;
|
||||||
|
sharedWithId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type SyncItem = {
|
export type SyncItem = {
|
||||||
[SyncEntityType.UserV1]: SyncUserV1;
|
[SyncEntityType.UserV1]: SyncUserV1;
|
||||||
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
||||||
|
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
||||||
|
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const responseDtos = [
|
const responseDtos = [
|
||||||
//
|
//
|
||||||
SyncUserV1,
|
SyncUserV1,
|
||||||
SyncUserDeleteV1,
|
SyncUserDeleteV1,
|
||||||
|
SyncPartnerV1,
|
||||||
|
SyncPartnerDeleteV1,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const extraSyncModels = responseDtos;
|
export const extraSyncModels = responseDtos;
|
||||||
|
19
server/src/entities/partner-audit.entity.ts
Normal file
19
server/src/entities/partner-audit.entity.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('partners_audit')
|
||||||
|
export class PartnerAuditEntity {
|
||||||
|
@PrimaryColumn({ type: 'uuid', nullable: false, default: () => 'immich_uuid_v7()' })
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Index('IDX_partners_audit_shared_by_id')
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
sharedById!: string;
|
||||||
|
|
||||||
|
@Index('IDX_partners_audit_shared_with_id')
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
sharedWithId!: string;
|
||||||
|
|
||||||
|
@Index('IDX_partners_audit_deleted_at')
|
||||||
|
@CreateDateColumn({ type: 'timestamptz', default: () => 'clock_timestamp()' })
|
||||||
|
deletedAt!: Date;
|
||||||
|
}
|
@ -548,9 +548,12 @@ export enum DatabaseLock {
|
|||||||
|
|
||||||
export enum SyncRequestType {
|
export enum SyncRequestType {
|
||||||
UsersV1 = 'UsersV1',
|
UsersV1 = 'UsersV1',
|
||||||
|
PartnersV1 = 'PartnersV1',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SyncEntityType {
|
export enum SyncEntityType {
|
||||||
UserV1 = 'UserV1',
|
UserV1 = 'UserV1',
|
||||||
UserDeleteV1 = 'UserDeleteV1',
|
UserDeleteV1 = 'UserDeleteV1',
|
||||||
|
PartnerV1 = 'PartnerV1',
|
||||||
|
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class CreatePartnersAuditTable1740739778549 implements MigrationInterface {
|
||||||
|
name = 'CreatePartnersAuditTable1740739778549'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT clock_timestamp(), CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_shared_by_id" ON "partners_audit" ("sharedById") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_shared_with_id" ON "partners_audit" ("sharedWithId") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_deleted_at" ON "partners_audit" ("deletedAt") `);
|
||||||
|
await queryRunner.query(`CREATE OR REPLACE FUNCTION partners_delete_audit() RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO partners_audit ("sharedById", "sharedWithId")
|
||||||
|
SELECT "sharedById", "sharedWithId"
|
||||||
|
FROM OLD;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql`
|
||||||
|
);
|
||||||
|
await queryRunner.query(`CREATE OR REPLACE TRIGGER partners_delete_audit
|
||||||
|
AFTER DELETE ON partners
|
||||||
|
REFERENCING OLD TABLE AS OLD
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION partners_delete_audit();
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_deleted_at"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_shared_with_id"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_shared_by_id"`);
|
||||||
|
await queryRunner.query(`DROP TRIGGER partners_delete_audit`);
|
||||||
|
await queryRunner.query(`DROP FUNCTION partners_delete_audit`);
|
||||||
|
await queryRunner.query(`DROP TABLE "partners_audit"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -56,4 +56,26 @@ export class SyncRepository {
|
|||||||
.orderBy(['id asc'])
|
.orderBy(['id asc'])
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPartnerUpserts(userId: string, ack?: SyncAck) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('partners')
|
||||||
|
.select(['sharedById', 'sharedWithId', 'inTimeline', 'updateId'])
|
||||||
|
.$if(!!ack, (qb) => qb.where('updateId', '>', ack!.updateId))
|
||||||
|
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
|
||||||
|
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||||
|
.orderBy(['updateId asc'])
|
||||||
|
.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPartnerDeletes(userId: string, ack?: SyncAck) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('partners_audit')
|
||||||
|
.select(['id', 'sharedById', 'sharedWithId'])
|
||||||
|
.$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId))
|
||||||
|
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
|
||||||
|
.where('deletedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||||
|
.orderBy(['id asc'])
|
||||||
|
.stream();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] };
|
|||||||
const SYNC_TYPES_ORDER = [
|
const SYNC_TYPES_ORDER = [
|
||||||
//
|
//
|
||||||
SyncRequestType.UsersV1,
|
SyncRequestType.UsersV1,
|
||||||
|
SyncRequestType.PartnersV1,
|
||||||
];
|
];
|
||||||
|
|
||||||
const throwSessionRequired = () => {
|
const throwSessionRequired = () => {
|
||||||
@ -81,8 +82,6 @@ export class SyncService extends BaseService {
|
|||||||
checkpoints.map(({ type, ack }) => [type, fromAck(ack)]),
|
checkpoints.map(({ type, ack }) => [type, fromAck(ack)]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO pre-filter/sort list based on optimal sync order
|
|
||||||
|
|
||||||
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SyncRequestType.UsersV1: {
|
case SyncRequestType.UsersV1: {
|
||||||
@ -99,6 +98,23 @@ export class SyncService extends BaseService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SyncRequestType.PartnersV1: {
|
||||||
|
const deletes = this.syncRepository.getPartnerDeletes(
|
||||||
|
auth.user.id,
|
||||||
|
checkpointMap[SyncEntityType.PartnerDeleteV1],
|
||||||
|
);
|
||||||
|
for await (const { id, ...data } of deletes) {
|
||||||
|
response.write(serialize({ type: SyncEntityType.PartnerDeleteV1, updateId: id, data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const upserts = this.syncRepository.getPartnerUpserts(auth.user.id, checkpointMap[SyncEntityType.PartnerV1]);
|
||||||
|
for await (const { updateId, ...data } of upserts) {
|
||||||
|
response.write(serialize({ type: SyncEntityType.PartnerV1, updateId, data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
this.logger.warn(`Unsupported sync type: ${type}`);
|
this.logger.warn(`Unsupported sync type: ${type}`);
|
||||||
break;
|
break;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Insertable, Kysely } from 'kysely';
|
import { Insertable, Kysely } from 'kysely';
|
||||||
import { randomBytes, randomUUID } from 'node:crypto';
|
import { randomBytes, randomUUID } from 'node:crypto';
|
||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { Assets, DB, Sessions, Users } from 'src/db';
|
import { Assets, DB, Partners, Sessions, Users } from 'src/db';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetType } from 'src/enum';
|
import { AssetType } from 'src/enum';
|
||||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
|
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||||
import { SessionRepository } from 'src/repositories/session.repository';
|
import { SessionRepository } from 'src/repositories/session.repository';
|
||||||
import { SyncRepository } from 'src/repositories/sync.repository';
|
import { SyncRepository } from 'src/repositories/sync.repository';
|
||||||
import { UserRepository } from 'src/repositories/user.repository';
|
import { UserRepository } from 'src/repositories/user.repository';
|
||||||
@ -30,6 +31,7 @@ class CustomWritable extends Writable {
|
|||||||
type Asset = Insertable<Assets>;
|
type Asset = Insertable<Assets>;
|
||||||
type User = Partial<Insertable<Users>>;
|
type User = Partial<Insertable<Users>>;
|
||||||
type Session = Omit<Insertable<Sessions>, 'token'> & { token?: string };
|
type Session = Omit<Insertable<Sessions>, 'token'> & { token?: string };
|
||||||
|
type Partner = Insertable<Partners>;
|
||||||
|
|
||||||
export const newUuid = () => randomUUID() as string;
|
export const newUuid = () => randomUUID() as string;
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ export class TestFactory {
|
|||||||
private assets: Asset[] = [];
|
private assets: Asset[] = [];
|
||||||
private sessions: Session[] = [];
|
private sessions: Session[] = [];
|
||||||
private users: User[] = [];
|
private users: User[] = [];
|
||||||
|
private partners: Partner[] = [];
|
||||||
|
|
||||||
private constructor(private context: TestContext) {}
|
private constructor(private context: TestContext) {}
|
||||||
|
|
||||||
@ -100,6 +103,17 @@ export class TestFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static partner(partner: Partner) {
|
||||||
|
const defaults = {
|
||||||
|
inTimeline: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaults,
|
||||||
|
...partner,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
withAsset(asset: Asset) {
|
withAsset(asset: Asset) {
|
||||||
this.assets.push(asset);
|
this.assets.push(asset);
|
||||||
return this;
|
return this;
|
||||||
@ -115,6 +129,11 @@ export class TestFactory {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withPartner(partner: Partner) {
|
||||||
|
this.partners.push(partner);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
for (const asset of this.assets) {
|
for (const asset of this.assets) {
|
||||||
await this.context.createAsset(asset);
|
await this.context.createAsset(asset);
|
||||||
@ -124,6 +143,10 @@ export class TestFactory {
|
|||||||
await this.context.createUser(user);
|
await this.context.createUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const partner of this.partners) {
|
||||||
|
await this.context.createPartner(partner);
|
||||||
|
}
|
||||||
|
|
||||||
for (const session of this.sessions) {
|
for (const session of this.sessions) {
|
||||||
await this.context.createSession(session);
|
await this.context.createSession(session);
|
||||||
}
|
}
|
||||||
@ -138,6 +161,7 @@ export class TestContext {
|
|||||||
albumRepository: AlbumRepository;
|
albumRepository: AlbumRepository;
|
||||||
sessionRepository: SessionRepository;
|
sessionRepository: SessionRepository;
|
||||||
syncRepository: SyncRepository;
|
syncRepository: SyncRepository;
|
||||||
|
partnerRepository: PartnerRepository;
|
||||||
|
|
||||||
private constructor(private db: Kysely<DB>) {
|
private constructor(private db: Kysely<DB>) {
|
||||||
this.userRepository = new UserRepository(this.db);
|
this.userRepository = new UserRepository(this.db);
|
||||||
@ -145,6 +169,7 @@ export class TestContext {
|
|||||||
this.albumRepository = new AlbumRepository(this.db);
|
this.albumRepository = new AlbumRepository(this.db);
|
||||||
this.sessionRepository = new SessionRepository(this.db);
|
this.sessionRepository = new SessionRepository(this.db);
|
||||||
this.syncRepository = new SyncRepository(this.db);
|
this.syncRepository = new SyncRepository(this.db);
|
||||||
|
this.partnerRepository = new PartnerRepository(this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
static from(db: Kysely<DB>) {
|
static from(db: Kysely<DB>) {
|
||||||
@ -159,6 +184,10 @@ export class TestContext {
|
|||||||
return this.userRepository.create(TestFactory.user(user));
|
return this.userRepository.create(TestFactory.user(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createPartner(partner: Partner) {
|
||||||
|
return this.partnerRepository.create(TestFactory.partner(partner));
|
||||||
|
}
|
||||||
|
|
||||||
createAsset(asset: Asset) {
|
createAsset(asset: Asset) {
|
||||||
return this.assetRepository.create(TestFactory.asset(asset));
|
return this.assetRepository.create(TestFactory.asset(asset));
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ const setup = async () => {
|
|||||||
|
|
||||||
const testSync = async (auth: AuthDto, types: SyncRequestType[]) => {
|
const testSync = async (auth: AuthDto, types: SyncRequestType[]) => {
|
||||||
const stream = TestFactory.stream();
|
const stream = TestFactory.stream();
|
||||||
|
// Wait for 1ms to ensure all updates are available
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
||||||
await sut.stream(auth, stream, { types });
|
await sut.stream(auth, stream, { types });
|
||||||
|
|
||||||
return stream.getResponse();
|
return stream.getResponse();
|
||||||
@ -186,4 +188,178 @@ describe(SyncService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.concurrent('partners', () => {
|
||||||
|
it('should detect and sync the first partner', async () => {
|
||||||
|
const { auth, context, sut, testSync } = await setup();
|
||||||
|
|
||||||
|
const user1 = auth.user;
|
||||||
|
const user2 = await context.createUser();
|
||||||
|
|
||||||
|
const partner = await context.createPartner({ sharedById: user2.id, sharedWithId: user1.id });
|
||||||
|
|
||||||
|
const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(initialSyncResponse).toHaveLength(1);
|
||||||
|
expect(initialSyncResponse).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
inTimeline: partner.inTimeline,
|
||||||
|
sharedById: partner.sharedById,
|
||||||
|
sharedWithId: partner.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerV1',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const acks = [initialSyncResponse[0].ack];
|
||||||
|
await sut.setAcks(auth, { acks });
|
||||||
|
|
||||||
|
const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(ackSyncResponse).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect and sync a deleted partner', async () => {
|
||||||
|
const { auth, context, sut, testSync } = await setup();
|
||||||
|
|
||||||
|
const user1 = auth.user;
|
||||||
|
const user2 = await context.createUser();
|
||||||
|
|
||||||
|
const partner = await context.createPartner({ sharedById: user2.id, sharedWithId: user1.id });
|
||||||
|
await context.partnerRepository.remove(partner);
|
||||||
|
|
||||||
|
const response = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(response).toHaveLength(1);
|
||||||
|
expect(response).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
sharedById: partner.sharedById,
|
||||||
|
sharedWithId: partner.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerDeleteV1',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const acks = response.map(({ ack }) => ack);
|
||||||
|
await sut.setAcks(auth, { acks });
|
||||||
|
|
||||||
|
const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(ackSyncResponse).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect and sync a partner share both to and from another user', async () => {
|
||||||
|
const { auth, context, sut, testSync } = await setup();
|
||||||
|
|
||||||
|
const user1 = auth.user;
|
||||||
|
const user2 = await context.createUser();
|
||||||
|
|
||||||
|
const partner1 = await context.createPartner({ sharedById: user2.id, sharedWithId: user1.id });
|
||||||
|
const partner2 = await context.createPartner({ sharedById: user1.id, sharedWithId: user2.id });
|
||||||
|
|
||||||
|
const response = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(response).toHaveLength(2);
|
||||||
|
expect(response).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
inTimeline: partner1.inTimeline,
|
||||||
|
sharedById: partner1.sharedById,
|
||||||
|
sharedWithId: partner1.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerV1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
inTimeline: partner2.inTimeline,
|
||||||
|
sharedById: partner2.sharedById,
|
||||||
|
sharedWithId: partner2.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerV1',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sut.setAcks(auth, { acks: [response[1].ack] });
|
||||||
|
|
||||||
|
const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(ackSyncResponse).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sync a partner and then an update to that same partner', async () => {
|
||||||
|
const { auth, context, sut, testSync } = await setup();
|
||||||
|
|
||||||
|
const user1 = auth.user;
|
||||||
|
const user2 = await context.createUser();
|
||||||
|
|
||||||
|
const partner = await context.createPartner({ sharedById: user2.id, sharedWithId: user1.id });
|
||||||
|
|
||||||
|
const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(initialSyncResponse).toHaveLength(1);
|
||||||
|
expect(initialSyncResponse).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
inTimeline: partner.inTimeline,
|
||||||
|
sharedById: partner.sharedById,
|
||||||
|
sharedWithId: partner.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerV1',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const acks = [initialSyncResponse[0].ack];
|
||||||
|
await sut.setAcks(auth, { acks });
|
||||||
|
|
||||||
|
const updated = await context.partnerRepository.update(
|
||||||
|
{ sharedById: partner.sharedById, sharedWithId: partner.sharedWithId },
|
||||||
|
{ inTimeline: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(updatedSyncResponse).toHaveLength(1);
|
||||||
|
expect(updatedSyncResponse).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
inTimeline: updated.inTimeline,
|
||||||
|
sharedById: updated.sharedById,
|
||||||
|
sharedWithId: updated.sharedWithId,
|
||||||
|
},
|
||||||
|
type: 'PartnerV1',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not sync a partner for an unrelated user', async () => {
|
||||||
|
const { auth, context, testSync } = await setup();
|
||||||
|
|
||||||
|
const user2 = await context.createUser();
|
||||||
|
const user3 = await context.createUser();
|
||||||
|
|
||||||
|
await context.createPartner({ sharedById: user2.id, sharedWithId: user3.id });
|
||||||
|
|
||||||
|
const response = await testSync(auth, [SyncRequestType.PartnersV1]);
|
||||||
|
|
||||||
|
expect(response).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,5 +9,7 @@ export const newSyncRepositoryMock = (): Mocked<RepositoryInterface<SyncReposito
|
|||||||
deleteCheckpoints: vitest.fn(),
|
deleteCheckpoints: vitest.fn(),
|
||||||
getUserUpserts: vitest.fn(),
|
getUserUpserts: vitest.fn(),
|
||||||
getUserDeletes: vitest.fn(),
|
getUserDeletes: vitest.fn(),
|
||||||
|
getPartnerUpserts: vitest.fn(),
|
||||||
|
getPartnerDeletes: vitest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user