diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 798d388d7..496fa5441 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2361,6 +2361,103 @@ export interface OAuthConfigResponseDto { */ 'url'?: string; } +/** + * + * @export + * @interface PartnerResponseDto + */ +export interface PartnerResponseDto { + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'deletedAt': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'email': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'externalPath': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'id': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'inTimeline'?: boolean; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'storageLabel': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'updatedAt': string; +} /** * * @export @@ -4220,6 +4317,19 @@ export interface UpdateLibraryDto { */ 'name'?: string; } +/** + * + * @export + * @interface UpdatePartnerDto + */ +export interface UpdatePartnerDto { + /** + * + * @type {boolean} + * @memberof UpdatePartnerDto + */ + 'inTimeline': boolean; +} /** * * @export @@ -7274,11 +7384,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBucket', 'size', size) // verify required parameter 'timeBucket' is not null or undefined @@ -7336,6 +7447,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (timeBucket !== undefined) { localVarQueryParameter['timeBucket'] = timeBucket; } @@ -7365,11 +7480,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBuckets', 'size', size) const localVarPath = `/asset/time-buckets`; @@ -7425,6 +7541,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (key !== undefined) { localVarQueryParameter['key'] = key; } @@ -8227,12 +8347,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8245,12 +8366,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8547,7 +8669,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * @@ -8556,7 +8678,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * Get all asset of a device that are in the database, ID only. @@ -9043,6 +9165,13 @@ export interface AssetApiGetTimeBucketRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBucket + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9113,6 +9242,13 @@ export interface AssetApiGetTimeBucketsRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9592,7 +9728,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9603,7 +9739,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -12312,6 +12448,54 @@ export const PartnerApiAxiosParamCreator = function (configuration?: Configurati let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner: async (id: string, updatePartnerDto: UpdatePartnerDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updatePartner', 'id', id) + // verify required parameter 'updatePartnerDto' is not null or undefined + assertParamExists('updatePartner', 'updatePartnerDto', updatePartnerDto) + const localVarPath = `/partner/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updatePartnerDto, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -12333,7 +12517,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.createPartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12343,7 +12527,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { const localVarAxiosArgs = await localVarAxiosParamCreator.getPartners(direction, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12357,6 +12541,17 @@ export const PartnerApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.removePartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updatePartner(id: string, updatePartnerDto: UpdatePartnerDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updatePartner(id, updatePartnerDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -12373,7 +12568,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createPartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, /** @@ -12382,7 +12577,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { + getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPartners(requestParameters.direction, options).then((request) => request(axios, basePath)); }, /** @@ -12394,6 +12589,15 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.removePartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(axios, basePath)); + }, }; }; @@ -12439,6 +12643,27 @@ export interface PartnerApiRemovePartnerRequest { readonly id: string } +/** + * Request parameters for updatePartner operation in PartnerApi. + * @export + * @interface PartnerApiUpdatePartnerRequest + */ +export interface PartnerApiUpdatePartnerRequest { + /** + * + * @type {string} + * @memberof PartnerApiUpdatePartner + */ + readonly id: string + + /** + * + * @type {UpdatePartnerDto} + * @memberof PartnerApiUpdatePartner + */ + readonly updatePartnerDto: UpdatePartnerDto +} + /** * PartnerApi - object-oriented interface * @export @@ -12478,6 +12703,17 @@ export class PartnerApi extends BaseAPI { public removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig) { return PartnerApiFp(this.configuration).removePartner(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PartnerApi + */ + public updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig) { + return PartnerApiFp(this.configuration).updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 33d2c0f3e..ad2103786 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -187,12 +187,15 @@ class AuthenticationNotifier extends StateNotifier { if (userResponseDto != null) { Store.put(StoreKey.deviceId, deviceId); Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); - Store.put(StoreKey.currentUser, User.fromDto(userResponseDto)); + Store.put( + StoreKey.currentUser, + User.fromUserDto(userResponseDto), + ); Store.put(StoreKey.serverUrl, serverUrl); Store.put(StoreKey.accessToken, accessToken); shouldChangePassword = userResponseDto.shouldChangePassword; - user = User.fromDto(userResponseDto); + user = User.fromUserDto(userResponseDto); retResult = true; } else { diff --git a/mobile/lib/modules/partner/services/partner.service.dart b/mobile/lib/modules/partner/services/partner.service.dart index 42fcdb438..0605e56ef 100644 --- a/mobile/lib/modules/partner/services/partner.service.dart +++ b/mobile/lib/modules/partner/services/partner.service.dart @@ -36,7 +36,7 @@ class PartnerService { final userDtos = await _apiService.partnerApi.getPartners(direction._value); if (userDtos != null) { - return userDtos.map((u) => User.fromDto(u)).toList(); + return userDtos.map((u) => User.fromPartnerDto(u)).toList(); } } catch (e) { _log.warning("failed to get partners for direction $direction:\n$e"); diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index 5a4ee4398..dafbedd31 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -63,7 +63,10 @@ class TabNavigationObserver extends AutoRouterObserver { return; } - Store.put(StoreKey.currentUser, User.fromDto(userResponseDto)); + Store.put( + StoreKey.currentUser, + User.fromUserDto(userResponseDto), + ); ref.read(serverInfoProvider.notifier).getServerVersion(); } catch (e) { debugPrint("Error refreshing user info $e"); diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart index df742a154..59f01c7dc 100644 --- a/mobile/lib/shared/models/user.dart +++ b/mobile/lib/shared/models/user.dart @@ -18,11 +18,12 @@ class User { this.isPartnerSharedWith = false, this.profileImagePath = '', this.memoryEnabled = true, + this.inTimeline = false, }); Id get isarId => fastHash(id); - User.fromDto(UserResponseDto dto) + User.fromUserDto(UserResponseDto dto) : id = dto.id, updatedAt = dto.updatedAt, email = dto.email, @@ -34,6 +35,19 @@ class User { isAdmin = dto.isAdmin, memoryEnabled = dto.memoriesEnabled; + User.fromPartnerDto(PartnerResponseDto dto) + : id = dto.id, + updatedAt = dto.updatedAt, + email = dto.email, + firstName = dto.firstName, + lastName = dto.lastName, + isPartnerSharedBy = false, + isPartnerSharedWith = false, + profileImagePath = dto.profileImagePath, + isAdmin = dto.isAdmin, + memoryEnabled = dto.memoriesEnabled, + inTimeline = dto.inTimeline; + @Index(unique: true, replace: false, type: IndexType.hash) String id; DateTime updatedAt; @@ -45,6 +59,8 @@ class User { bool isAdmin; String profileImagePath; bool? memoryEnabled; + bool? inTimeline; + @Backlink(to: 'owner') final IsarLinks albums = IsarLinks(); @Backlink(to: 'sharedUsers') @@ -62,7 +78,8 @@ class User { isPartnerSharedWith == other.isPartnerSharedWith && profileImagePath == other.profileImagePath && isAdmin == other.isAdmin && - memoryEnabled == other.memoryEnabled; + memoryEnabled == other.memoryEnabled && + inTimeline == other.inTimeline; } @override @@ -77,5 +94,6 @@ class User { isPartnerSharedWith.hashCode ^ profileImagePath.hashCode ^ isAdmin.hashCode ^ - memoryEnabled.hashCode; + memoryEnabled.hashCode ^ + inTimeline.hashCode; } diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index 687a784c0..af3bdadf6 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -32,38 +32,43 @@ const UserSchema = CollectionSchema( name: r'id', type: IsarType.string, ), - r'isAdmin': PropertySchema( + r'inTimeline': PropertySchema( id: 3, + name: r'inTimeline', + type: IsarType.bool, + ), + r'isAdmin': PropertySchema( + id: 4, name: r'isAdmin', type: IsarType.bool, ), r'isPartnerSharedBy': PropertySchema( - id: 4, + id: 5, name: r'isPartnerSharedBy', type: IsarType.bool, ), r'isPartnerSharedWith': PropertySchema( - id: 5, + id: 6, name: r'isPartnerSharedWith', type: IsarType.bool, ), r'lastName': PropertySchema( - id: 6, + id: 7, name: r'lastName', type: IsarType.string, ), r'memoryEnabled': PropertySchema( - id: 7, + id: 8, name: r'memoryEnabled', type: IsarType.bool, ), r'profileImagePath': PropertySchema( - id: 8, + id: 9, name: r'profileImagePath', type: IsarType.string, ), r'updatedAt': PropertySchema( - id: 9, + id: 10, name: r'updatedAt', type: IsarType.dateTime, ) @@ -134,13 +139,14 @@ void _userSerialize( writer.writeString(offsets[0], object.email); writer.writeString(offsets[1], object.firstName); writer.writeString(offsets[2], object.id); - writer.writeBool(offsets[3], object.isAdmin); - writer.writeBool(offsets[4], object.isPartnerSharedBy); - writer.writeBool(offsets[5], object.isPartnerSharedWith); - writer.writeString(offsets[6], object.lastName); - writer.writeBool(offsets[7], object.memoryEnabled); - writer.writeString(offsets[8], object.profileImagePath); - writer.writeDateTime(offsets[9], object.updatedAt); + writer.writeBool(offsets[3], object.inTimeline); + writer.writeBool(offsets[4], object.isAdmin); + writer.writeBool(offsets[5], object.isPartnerSharedBy); + writer.writeBool(offsets[6], object.isPartnerSharedWith); + writer.writeString(offsets[7], object.lastName); + writer.writeBool(offsets[8], object.memoryEnabled); + writer.writeString(offsets[9], object.profileImagePath); + writer.writeDateTime(offsets[10], object.updatedAt); } User _userDeserialize( @@ -153,13 +159,14 @@ User _userDeserialize( email: reader.readString(offsets[0]), firstName: reader.readString(offsets[1]), id: reader.readString(offsets[2]), - isAdmin: reader.readBool(offsets[3]), - isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, - isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, - lastName: reader.readString(offsets[6]), - memoryEnabled: reader.readBoolOrNull(offsets[7]), - profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', - updatedAt: reader.readDateTime(offsets[9]), + inTimeline: reader.readBoolOrNull(offsets[3]), + isAdmin: reader.readBool(offsets[4]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, + lastName: reader.readString(offsets[7]), + memoryEnabled: reader.readBoolOrNull(offsets[8]), + profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', + updatedAt: reader.readDateTime(offsets[10]), ); return object; } @@ -178,18 +185,20 @@ P _userDeserializeProp

( case 2: return (reader.readString(offset)) as P; case 3: - return (reader.readBool(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 4: - return (reader.readBoolOrNull(offset) ?? false) as P; + return (reader.readBool(offset)) as P; case 5: return (reader.readBoolOrNull(offset) ?? false) as P; case 6: - return (reader.readString(offset)) as P; + return (reader.readBoolOrNull(offset) ?? false) as P; case 7: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readString(offset)) as P; case 8: - return (reader.readStringOrNull(offset) ?? '') as P; + return (reader.readBoolOrNull(offset)) as P; case 9: + return (reader.readStringOrNull(offset) ?? '') as P; + case 10: return (reader.readDateTime(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -769,6 +778,32 @@ extension UserQueryFilter on QueryBuilder { }); } + QueryBuilder inTimelineIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'inTimeline', + )); + }); + } + + QueryBuilder inTimelineIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'inTimeline', + )); + }); + } + + QueryBuilder inTimelineEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'inTimeline', + value: value, + )); + }); + } + QueryBuilder isAdminEqualTo(bool value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( @@ -1341,6 +1376,18 @@ extension UserQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder sortByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + QueryBuilder sortByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isAdmin', Sort.asc); @@ -1463,6 +1510,18 @@ extension UserQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder thenByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + QueryBuilder thenByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isAdmin', Sort.asc); @@ -1582,6 +1641,12 @@ extension UserQueryWhereDistinct on QueryBuilder { }); } + QueryBuilder distinctByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'inTimeline'); + }); + } + QueryBuilder distinctByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'isAdmin'); @@ -1653,6 +1718,12 @@ extension UserQueryProperty on QueryBuilder { }); } + QueryBuilder inTimelineProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'inTimeline'); + }); + } + QueryBuilder isAdminProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'isAdmin'); diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart index 51e3cbc25..16f7b7b0e 100644 --- a/mobile/lib/shared/services/user.service.dart +++ b/mobile/lib/shared/services/user.service.dart @@ -40,7 +40,7 @@ class UserService { Future?> _getAllUsers({required bool isAll}) async { try { final dto = await _apiService.userApi.getAllUsers(isAll); - return dto?.map(User.fromDto).toList(); + return dto?.map(User.fromUserDto).toList(); } catch (e) { _log.warning("Failed get all users:\n$e"); return null; diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 57854e1b7..c4f967976 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -91,6 +91,7 @@ doc/OAuthCallbackDto.md doc/OAuthConfigDto.md doc/OAuthConfigResponseDto.md doc/PartnerApi.md +doc/PartnerResponseDto.md doc/PathEntityType.md doc/PathType.md doc/PeopleResponseDto.md @@ -159,6 +160,7 @@ doc/TranscodePolicy.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md doc/UpdateLibraryDto.md +doc/UpdatePartnerDto.md doc/UpdateStackParentDto.md doc/UpdateTagDto.md doc/UpdateUserDto.md @@ -273,6 +275,7 @@ lib/model/o_auth_authorize_response_dto.dart lib/model/o_auth_callback_dto.dart lib/model/o_auth_config_dto.dart lib/model/o_auth_config_response_dto.dart +lib/model/partner_response_dto.dart lib/model/path_entity_type.dart lib/model/path_type.dart lib/model/people_response_dto.dart @@ -335,6 +338,7 @@ lib/model/transcode_policy.dart lib/model/update_album_dto.dart lib/model/update_asset_dto.dart lib/model/update_library_dto.dart +lib/model/update_partner_dto.dart lib/model/update_stack_parent_dto.dart lib/model/update_tag_dto.dart lib/model/update_user_dto.dart @@ -432,6 +436,7 @@ test/o_auth_callback_dto_test.dart test/o_auth_config_dto_test.dart test/o_auth_config_response_dto_test.dart test/partner_api_test.dart +test/partner_response_dto_test.dart test/path_entity_type_test.dart test/path_type_test.dart test/people_response_dto_test.dart @@ -500,6 +505,7 @@ test/transcode_policy_test.dart test/update_album_dto_test.dart test/update_asset_dto_test.dart test/update_library_dto_test.dart +test/update_partner_dto_test.dart test/update_stack_parent_dto_test.dart test/update_tag_dto_test.dart test/update_user_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 0bc2cc1e3..551169a99 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -152,6 +152,7 @@ Class | Method | HTTP request | Description *PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partner/{id} | *PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner | *PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} | +*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partner/{id} | *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | *PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | *PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | @@ -283,6 +284,7 @@ Class | Method | HTTP request | Description - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) + - [PartnerResponseDto](doc//PartnerResponseDto.md) - [PathEntityType](doc//PathEntityType.md) - [PathType](doc//PathType.md) - [PeopleResponseDto](doc//PeopleResponseDto.md) @@ -345,6 +347,7 @@ Class | Method | HTTP request | Description - [UpdateAlbumDto](doc//UpdateAlbumDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateLibraryDto](doc//UpdateLibraryDto.md) + - [UpdatePartnerDto](doc//UpdatePartnerDto.md) - [UpdateStackParentDto](doc//UpdateStackParentDto.md) - [UpdateTagDto](doc//UpdateTagDto.md) - [UpdateUserDto](doc//UpdateUserDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 811841947..2938a075b 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -1005,7 +1005,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBucket** -> List getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key) +> List getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key) @@ -1037,10 +1037,11 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final withStacked = true; // bool | +final withPartners = true; // bool | final key = key_example; // String | try { - final result = api_instance.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key); + final result = api_instance.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBucket: $e\n'); @@ -1060,6 +1061,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **withStacked** | **bool**| | [optional] + **withPartners** | **bool**| | [optional] **key** | **String**| | [optional] ### Return type @@ -1078,7 +1080,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBuckets** -> List getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key) +> List getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key) @@ -1109,10 +1111,11 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final withStacked = true; // bool | +final withPartners = true; // bool | final key = key_example; // String | try { - final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key); + final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBuckets: $e\n'); @@ -1131,6 +1134,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **withStacked** | **bool**| | [optional] + **withPartners** | **bool**| | [optional] **key** | **String**| | [optional] ### Return type diff --git a/mobile/openapi/doc/PartnerApi.md b/mobile/openapi/doc/PartnerApi.md index 937978bef..5b4624e0a 100644 --- a/mobile/openapi/doc/PartnerApi.md +++ b/mobile/openapi/doc/PartnerApi.md @@ -12,10 +12,11 @@ Method | HTTP request | Description [**createPartner**](PartnerApi.md#createpartner) | **POST** /partner/{id} | [**getPartners**](PartnerApi.md#getpartners) | **GET** /partner | [**removePartner**](PartnerApi.md#removepartner) | **DELETE** /partner/{id} | +[**updatePartner**](PartnerApi.md#updatepartner) | **PUT** /partner/{id} | # **createPartner** -> UserResponseDto createPartner(id) +> PartnerResponseDto createPartner(id) @@ -56,7 +57,7 @@ Name | Type | Description | Notes ### Return type -[**UserResponseDto**](UserResponseDto.md) +[**PartnerResponseDto**](PartnerResponseDto.md) ### Authorization @@ -70,7 +71,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getPartners** -> List getPartners(direction) +> List getPartners(direction) @@ -111,7 +112,7 @@ Name | Type | Description | Notes ### Return type -[**List**](UserResponseDto.md) +[**List**](PartnerResponseDto.md) ### Authorization @@ -178,3 +179,60 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **updatePartner** +> PartnerResponseDto updatePartner(id, updatePartnerDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = PartnerApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final updatePartnerDto = UpdatePartnerDto(); // UpdatePartnerDto | + +try { + final result = api_instance.updatePartner(id, updatePartnerDto); + print(result); +} catch (e) { + print('Exception when calling PartnerApi->updatePartner: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **updatePartnerDto** | [**UpdatePartnerDto**](UpdatePartnerDto.md)| | + +### Return type + +[**PartnerResponseDto**](PartnerResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/doc/PartnerResponseDto.md b/mobile/openapi/doc/PartnerResponseDto.md new file mode 100644 index 000000000..887664e89 --- /dev/null +++ b/mobile/openapi/doc/PartnerResponseDto.md @@ -0,0 +1,29 @@ +# openapi.model.PartnerResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**createdAt** | [**DateTime**](DateTime.md) | | +**deletedAt** | [**DateTime**](DateTime.md) | | +**email** | **String** | | +**externalPath** | **String** | | +**firstName** | **String** | | +**id** | **String** | | +**inTimeline** | **bool** | | [optional] +**isAdmin** | **bool** | | +**lastName** | **String** | | +**memoriesEnabled** | **bool** | | [optional] +**oauthId** | **String** | | +**profileImagePath** | **String** | | +**shouldChangePassword** | **bool** | | +**storageLabel** | **String** | | +**updatedAt** | [**DateTime**](DateTime.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/UpdatePartnerDto.md b/mobile/openapi/doc/UpdatePartnerDto.md new file mode 100644 index 000000000..b336c419e --- /dev/null +++ b/mobile/openapi/doc/UpdatePartnerDto.md @@ -0,0 +1,15 @@ +# openapi.model.UpdatePartnerDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**inTimeline** | **bool** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 7e4ed3a7b..ed0b51f88 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -124,6 +124,7 @@ part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_config_response_dto.dart'; +part 'model/partner_response_dto.dart'; part 'model/path_entity_type.dart'; part 'model/path_type.dart'; part 'model/people_response_dto.dart'; @@ -186,6 +187,7 @@ part 'model/transcode_policy.dart'; part 'model/update_album_dto.dart'; part 'model/update_asset_dto.dart'; part 'model/update_library_dto.dart'; +part 'model/update_partner_dto.dart'; part 'model/update_stack_parent_dto.dart'; part 'model/update_tag_dto.dart'; part 'model/update_user_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 0d3c2bfe8..429a320c2 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -997,8 +997,10 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { + Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-bucket'; @@ -1030,6 +1032,9 @@ class AssetApi { } if (withStacked != null) { queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); } queryParams.addAll(_queryParams('', 'timeBucket', timeBucket)); if (key != null) { @@ -1070,9 +1075,11 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { - final response = await getTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, key: key, ); + Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { + final response = await getTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, withPartners: withPartners, key: key, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1108,8 +1115,10 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { + Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-buckets'; @@ -1142,6 +1151,9 @@ class AssetApi { if (withStacked != null) { queryParams.addAll(_queryParams('', 'withStacked', withStacked)); } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } if (key != null) { queryParams.addAll(_queryParams('', 'key', key)); } @@ -1178,9 +1190,11 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { - final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, key: key, ); + Future?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { + final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, withPartners: withPartners, key: key, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/partner_api.dart b/mobile/openapi/lib/api/partner_api.dart index cf374aafe..886b37a5b 100644 --- a/mobile/openapi/lib/api/partner_api.dart +++ b/mobile/openapi/lib/api/partner_api.dart @@ -49,7 +49,7 @@ class PartnerApi { /// Parameters: /// /// * [String] id (required): - Future createPartner(String id,) async { + Future createPartner(String id,) async { final response = await createPartnerWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -58,7 +58,7 @@ class PartnerApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; } return null; @@ -98,7 +98,7 @@ class PartnerApi { /// Parameters: /// /// * [String] direction (required): - Future?> getPartners(String direction,) async { + Future?> getPartners(String direction,) async { final response = await getPartnersWithHttpInfo(direction,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -108,8 +108,8 @@ class PartnerApi { // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() .toList(); } @@ -155,4 +155,56 @@ class PartnerApi { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } } + + /// Performs an HTTP 'PUT /partner/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdatePartnerDto] updatePartnerDto (required): + Future updatePartnerWithHttpInfo(String id, UpdatePartnerDto updatePartnerDto,) async { + // ignore: prefer_const_declarations + final path = r'/partner/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updatePartnerDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdatePartnerDto] updatePartnerDto (required): + Future updatePartner(String id, UpdatePartnerDto updatePartnerDto,) async { + final response = await updatePartnerWithHttpInfo(id, updatePartnerDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index dfdd5b4ef..c03a469ae 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -337,6 +337,8 @@ class ApiClient { return OAuthConfigDto.fromJson(value); case 'OAuthConfigResponseDto': return OAuthConfigResponseDto.fromJson(value); + case 'PartnerResponseDto': + return PartnerResponseDto.fromJson(value); case 'PathEntityType': return PathEntityTypeTypeTransformer().decode(value); case 'PathType': @@ -461,6 +463,8 @@ class ApiClient { return UpdateAssetDto.fromJson(value); case 'UpdateLibraryDto': return UpdateLibraryDto.fromJson(value); + case 'UpdatePartnerDto': + return UpdatePartnerDto.fromJson(value); case 'UpdateStackParentDto': return UpdateStackParentDto.fromJson(value); case 'UpdateTagDto': diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart new file mode 100644 index 000000000..ae6e23098 --- /dev/null +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -0,0 +1,240 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 PartnerResponseDto { + /// Returns a new [PartnerResponseDto] instance. + PartnerResponseDto({ + required this.createdAt, + required this.deletedAt, + required this.email, + required this.externalPath, + required this.firstName, + required this.id, + this.inTimeline, + required this.isAdmin, + required this.lastName, + this.memoriesEnabled, + required this.oauthId, + required this.profileImagePath, + required this.shouldChangePassword, + required this.storageLabel, + required this.updatedAt, + }); + + DateTime createdAt; + + DateTime? deletedAt; + + String email; + + String? externalPath; + + String firstName; + + String id; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? inTimeline; + + bool isAdmin; + + String lastName; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? memoriesEnabled; + + String oauthId; + + String profileImagePath; + + bool shouldChangePassword; + + String? storageLabel; + + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto && + other.createdAt == createdAt && + other.deletedAt == deletedAt && + other.email == email && + other.externalPath == externalPath && + other.firstName == firstName && + other.id == id && + other.inTimeline == inTimeline && + other.isAdmin == isAdmin && + other.lastName == lastName && + other.memoriesEnabled == memoriesEnabled && + other.oauthId == oauthId && + other.profileImagePath == profileImagePath && + other.shouldChangePassword == shouldChangePassword && + other.storageLabel == storageLabel && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (email.hashCode) + + (externalPath == null ? 0 : externalPath!.hashCode) + + (firstName.hashCode) + + (id.hashCode) + + (inTimeline == null ? 0 : inTimeline!.hashCode) + + (isAdmin.hashCode) + + (lastName.hashCode) + + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (oauthId.hashCode) + + (profileImagePath.hashCode) + + (shouldChangePassword.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, firstName=$firstName, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, lastName=$lastName, memoriesEnabled=$memoriesEnabled, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'email'] = this.email; + if (this.externalPath != null) { + json[r'externalPath'] = this.externalPath; + } else { + // json[r'externalPath'] = null; + } + json[r'firstName'] = this.firstName; + json[r'id'] = this.id; + if (this.inTimeline != null) { + json[r'inTimeline'] = this.inTimeline; + } else { + // json[r'inTimeline'] = null; + } + json[r'isAdmin'] = this.isAdmin; + json[r'lastName'] = this.lastName; + if (this.memoriesEnabled != null) { + json[r'memoriesEnabled'] = this.memoriesEnabled; + } else { + // json[r'memoriesEnabled'] = null; + } + json[r'oauthId'] = this.oauthId; + json[r'profileImagePath'] = this.profileImagePath; + json[r'shouldChangePassword'] = this.shouldChangePassword; + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [PartnerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PartnerResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return PartnerResponseDto( + createdAt: mapDateTime(json, r'createdAt', '')!, + deletedAt: mapDateTime(json, r'deletedAt', ''), + email: mapValueOfType(json, r'email')!, + externalPath: mapValueOfType(json, r'externalPath'), + firstName: mapValueOfType(json, r'firstName')!, + id: mapValueOfType(json, r'id')!, + inTimeline: mapValueOfType(json, r'inTimeline'), + isAdmin: mapValueOfType(json, r'isAdmin')!, + lastName: mapValueOfType(json, r'lastName')!, + memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + oauthId: mapValueOfType(json, r'oauthId')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, + storageLabel: mapValueOfType(json, r'storageLabel'), + updatedAt: mapDateTime(json, r'updatedAt', '')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PartnerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PartnerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PartnerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'deletedAt', + 'email', + 'externalPath', + 'firstName', + 'id', + 'isAdmin', + 'lastName', + 'oauthId', + 'profileImagePath', + 'shouldChangePassword', + 'storageLabel', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/update_partner_dto.dart b/mobile/openapi/lib/model/update_partner_dto.dart new file mode 100644 index 000000000..6a8aaf161 --- /dev/null +++ b/mobile/openapi/lib/model/update_partner_dto.dart @@ -0,0 +1,98 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 UpdatePartnerDto { + /// Returns a new [UpdatePartnerDto] instance. + UpdatePartnerDto({ + required this.inTimeline, + }); + + bool inTimeline; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdatePartnerDto && + other.inTimeline == inTimeline; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (inTimeline.hashCode); + + @override + String toString() => 'UpdatePartnerDto[inTimeline=$inTimeline]'; + + Map toJson() { + final json = {}; + json[r'inTimeline'] = this.inTimeline; + return json; + } + + /// Returns a new [UpdatePartnerDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdatePartnerDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return UpdatePartnerDto( + inTimeline: mapValueOfType(json, r'inTimeline')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdatePartnerDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdatePartnerDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdatePartnerDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdatePartnerDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'inTimeline', + }; +} + diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index e241c34ca..ec7a2182d 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -110,12 +110,12 @@ void main() { // TODO }); - //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, String key }) async + //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, bool withPartners, String key }) async test('test getTimeBucket', () async { // TODO }); - //Future> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, String key }) async + //Future> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, bool withPartners, String key }) async test('test getTimeBuckets', () async { // TODO }); diff --git a/mobile/openapi/test/partner_api_test.dart b/mobile/openapi/test/partner_api_test.dart index fa5a59d2a..daaf65644 100644 --- a/mobile/openapi/test/partner_api_test.dart +++ b/mobile/openapi/test/partner_api_test.dart @@ -17,12 +17,12 @@ void main() { // final instance = PartnerApi(); group('tests for PartnerApi', () { - //Future createPartner(String id) async + //Future createPartner(String id) async test('test createPartner', () async { // TODO }); - //Future> getPartners(String direction) async + //Future> getPartners(String direction) async test('test getPartners', () async { // TODO }); @@ -32,5 +32,10 @@ void main() { // TODO }); + //Future updatePartner(String id, UpdatePartnerDto updatePartnerDto) async + test('test updatePartner', () async { + // TODO + }); + }); } diff --git a/mobile/openapi/test/partner_response_dto_test.dart b/mobile/openapi/test/partner_response_dto_test.dart new file mode 100644 index 000000000..14092a44f --- /dev/null +++ b/mobile/openapi/test/partner_response_dto_test.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for PartnerResponseDto +void main() { + // final instance = PartnerResponseDto(); + + group('test PartnerResponseDto', () { + // DateTime createdAt + test('to test the property `createdAt`', () async { + // TODO + }); + + // DateTime deletedAt + test('to test the property `deletedAt`', () async { + // TODO + }); + + // String email + test('to test the property `email`', () async { + // TODO + }); + + // String externalPath + test('to test the property `externalPath`', () async { + // TODO + }); + + // String firstName + test('to test the property `firstName`', () async { + // TODO + }); + + // String id + test('to test the property `id`', () async { + // TODO + }); + + // bool inTimeline + test('to test the property `inTimeline`', () async { + // TODO + }); + + // bool isAdmin + test('to test the property `isAdmin`', () async { + // TODO + }); + + // String lastName + test('to test the property `lastName`', () async { + // TODO + }); + + // bool memoriesEnabled + test('to test the property `memoriesEnabled`', () async { + // TODO + }); + + // String oauthId + test('to test the property `oauthId`', () async { + // TODO + }); + + // String profileImagePath + test('to test the property `profileImagePath`', () async { + // TODO + }); + + // bool shouldChangePassword + test('to test the property `shouldChangePassword`', () async { + // TODO + }); + + // String storageLabel + test('to test the property `storageLabel`', () async { + // TODO + }); + + // DateTime updatedAt + test('to test the property `updatedAt`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/update_partner_dto_test.dart b/mobile/openapi/test/update_partner_dto_test.dart new file mode 100644 index 000000000..ca569914b --- /dev/null +++ b/mobile/openapi/test/update_partner_dto_test.dart @@ -0,0 +1,27 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for UpdatePartnerDto +void main() { + // final instance = UpdatePartnerDto(); + + group('test UpdatePartnerDto', () { + // bool inTimeline + test('to test the property `inTimeline`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 193586cbd..782b69649 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2071,6 +2071,14 @@ "type": "boolean" } }, + { + "name": "withPartners", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "timeBucket", "required": true, @@ -2199,6 +2207,14 @@ "type": "boolean" } }, + { + "name": "withPartners", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "key", "required": false, @@ -3491,7 +3507,7 @@ "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/UserResponseDto" + "$ref": "#/components/schemas/PartnerResponseDto" }, "type": "array" } @@ -3568,7 +3584,57 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserResponseDto" + "$ref": "#/components/schemas/PartnerResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Partner" + ] + }, + "put": { + "operationId": "updatePartner", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePartnerDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerResponseDto" } } }, @@ -7572,6 +7638,77 @@ ], "type": "object" }, + "PartnerResponseDto": { + "properties": { + "createdAt": { + "format": "date-time", + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "email": { + "type": "string" + }, + "externalPath": { + "nullable": true, + "type": "string" + }, + "firstName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "inTimeline": { + "type": "boolean" + }, + "isAdmin": { + "type": "boolean" + }, + "lastName": { + "type": "string" + }, + "memoriesEnabled": { + "type": "boolean" + }, + "oauthId": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "storageLabel": { + "nullable": true, + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "firstName", + "lastName", + "email", + "profileImagePath", + "storageLabel", + "externalPath", + "shouldChangePassword", + "isAdmin", + "createdAt", + "deletedAt", + "updatedAt", + "oauthId" + ], + "type": "object" + }, "PathEntityType": { "enum": [ "asset", @@ -8982,6 +9119,17 @@ }, "type": "object" }, + "UpdatePartnerDto": { + "properties": { + "inTimeline": { + "type": "boolean" + } + }, + "required": [ + "inTimeline" + ], + "type": "object" + }, "UpdateStackParentDto": { "properties": { "newParentId": { diff --git a/server/src/domain/access/access.core.ts b/server/src/domain/access/access.core.ts index 88abd79b1..16527de98 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/domain/access/access.core.ts @@ -40,6 +40,8 @@ export enum Permission { PERSON_READ = 'person.read', PERSON_WRITE = 'person.write', PERSON_MERGE = 'person.merge', + + PARTNER_UPDATE = 'partner.update', } let instance: AccessCore | null; @@ -242,6 +244,9 @@ export class AccessCore { case Permission.PERSON_MERGE: return this.repository.person.hasOwnerAccess(authUser.id, id); + case Permission.PARTNER_UPDATE: + return this.repository.partner.hasUpdateAccess(authUser.id, id); + default: return false; } diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 7b93935f6..45687282f 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -10,6 +10,7 @@ import { newCommunicationRepositoryMock, newCryptoRepositoryMock, newJobRepositoryMock, + newPartnerRepositoryMock, newStorageRepositoryMock, newSystemConfigRepositoryMock, } from '@test'; @@ -23,6 +24,7 @@ import { ICommunicationRepository, ICryptoRepository, IJobRepository, + IPartnerRepository, IStorageRepository, ISystemConfigRepository, JobItem, @@ -164,6 +166,7 @@ describe(AssetService.name, () => { let storageMock: jest.Mocked; let communicationMock: jest.Mocked; let configMock: jest.Mocked; + let partnerMock: jest.Mocked; it('should work', () => { expect(sut).toBeDefined(); @@ -177,7 +180,18 @@ describe(AssetService.name, () => { jobMock = newJobRepositoryMock(); storageMock = newStorageRepositoryMock(); configMock = newSystemConfigRepositoryMock(); - sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, configMock, storageMock, communicationMock); + partnerMock = newPartnerRepositoryMock(); + + sut = new AssetService( + accessMock, + assetMock, + cryptoMock, + jobMock, + configMock, + storageMock, + communicationMock, + partnerMock, + ); when(assetMock.getById) .calledWith(assetStub.livePhotoStillAsset.id) @@ -327,7 +341,7 @@ describe(AssetService.name, () => { size: TimeBucketSize.DAY, }), ).resolves.toEqual(expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }])); - expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userId: authStub.admin.id }); + expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userIds: [authStub.admin.id] }); }); }); @@ -363,7 +377,7 @@ describe(AssetService.name, () => { size: TimeBucketSize.DAY, timeBucket: 'bucket', isArchived: true, - userId: authStub.admin.id, + userIds: [authStub.admin.id], }); }); @@ -380,9 +394,65 @@ describe(AssetService.name, () => { expect(assetMock.getTimeBucket).toBeCalledWith('bucket', { size: TimeBucketSize.DAY, timeBucket: 'bucket', - userId: authStub.admin.id, + userIds: [authStub.admin.id], }); }); + + it('should throw an error if withParners is true and isArchived true or undefined', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isArchived: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isArchived: undefined, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); + + it('should throw an error if withParners is true and isFavorite is either true or false', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isFavorite: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isFavorite: false, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); + + it('should throw an error if withParners is true and isTrash is true', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isTrashed: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); }); describe('downloadFile', () => { diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index 34da78680..3b9b56412 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -16,9 +16,11 @@ import { ICommunicationRepository, ICryptoRepository, IJobRepository, + IPartnerRepository, IStorageRepository, ISystemConfigRepository, ImmichReadStream, + TimeBucketOptions, } from '../repositories'; import { StorageCore, StorageFolder } from '../storage'; import { SystemConfigCore } from '../system-config'; @@ -83,6 +85,7 @@ export class AssetService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository, + @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, ) { this.access = AccessCore.create(accessRepository); this.configCore = SystemConfigCore.create(configRepository); @@ -187,11 +190,25 @@ export class AssetService { await this.access.requirePermission(authUser, Permission.ARCHIVE_READ, [dto.userId]); } } + + if (dto.withPartners) { + const requestedArchived = dto.isArchived === true || dto.isArchived === undefined; + const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false; + const requestedTrash = dto.isTrashed === true; + + if (requestedArchived || requestedFavorite || requestedTrash) { + throw new BadRequestException( + 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + ); + } + } } async getTimeBuckets(authUser: AuthUserDto, dto: TimeBucketDto): Promise { await this.timeBucketChecks(authUser, dto); - return this.assetRepository.getTimeBuckets(dto); + const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto); + + return this.assetRepository.getTimeBuckets(timeBucketOptions); } async getTimeBucket( @@ -199,7 +216,8 @@ export class AssetService { dto: TimeBucketAssetDto, ): Promise { await this.timeBucketChecks(authUser, dto); - const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, dto); + const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto); + const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions); if (authUser.isShowMetadata) { return assets.map((asset) => mapAsset(asset, { withStack: true })); } else { @@ -207,6 +225,25 @@ export class AssetService { } } + async buildTimeBucketOptions(authUser: AuthUserDto, dto: TimeBucketDto): Promise { + const { userId, ...options } = dto; + let userIds: string[] | undefined = undefined; + + if (userId) { + userIds = [userId]; + + if (dto.withPartners) { + const partners = await this.partnerRepository.getAll(authUser.id); + const partnersIds = partners + .filter((partner) => partner.sharedBy && partner.sharedWith && partner.inTimeline) + .map((partner) => partner.sharedById); + + userIds.push(...partnersIds); + } + } + + return { ...options, userIds }; + } async downloadFile(authUser: AuthUserDto, id: string): Promise { await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, id); diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts index db2f1ecd0..849b8713f 100644 --- a/server/src/domain/asset/dto/time-bucket.dto.ts +++ b/server/src/domain/asset/dto/time-bucket.dto.ts @@ -38,6 +38,11 @@ export class TimeBucketDto { @IsBoolean() @Transform(toBoolean) withStacked?: boolean; + + @Optional() + @IsBoolean() + @Transform(toBoolean) + withPartners?: boolean; } export class TimeBucketAssetDto extends TimeBucketDto { diff --git a/server/src/domain/partner/partner.dto.ts b/server/src/domain/partner/partner.dto.ts new file mode 100644 index 000000000..17afcad5d --- /dev/null +++ b/server/src/domain/partner/partner.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty } from 'class-validator'; +import { UserResponseDto } from '../user'; + +export class UpdatePartnerDto { + @IsNotEmpty() + inTimeline!: boolean; +} + +export class PartnerResponseDto extends UserResponseDto { + inTimeline?: boolean; +} diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 2bc561194..0bae95aa7 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -1,11 +1,11 @@ import { BadRequestException } from '@nestjs/common'; import { authStub, newPartnerRepositoryMock, partnerStub } from '@test'; -import { UserResponseDto } from '../index'; -import { IPartnerRepository, PartnerDirection } from '../repositories'; +import { IAccessRepository, IPartnerRepository, PartnerDirection } from '../repositories'; +import { PartnerResponseDto } from './partner.dto'; import { PartnerService } from './partner.service'; const responseDto = { - admin: { + admin: { email: 'admin@test.com', firstName: 'admin_first_name', id: 'admin_id', @@ -20,8 +20,9 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + inTimeline: true, }, - user1: { + user1: { email: 'immich@test.com', firstName: 'immich_first_name', id: 'user-id', @@ -36,16 +37,18 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + inTimeline: true, }, }; describe(PartnerService.name, () => { let sut: PartnerService; let partnerMock: jest.Mocked; + let accessMock: jest.Mocked; beforeEach(async () => { partnerMock = newPartnerRepositoryMock(); - sut = new PartnerService(partnerMock); + sut = new PartnerService(partnerMock, accessMock); }); it('should work', () => { diff --git a/server/src/domain/partner/partner.service.ts b/server/src/domain/partner/partner.service.ts index 797938a8d..93600a5c0 100644 --- a/server/src/domain/partner/partner.service.ts +++ b/server/src/domain/partner/partner.service.ts @@ -1,14 +1,22 @@ import { PartnerEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { AccessCore, Permission } from '../access'; import { AuthUserDto } from '../auth'; -import { IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories'; -import { UserResponseDto, mapUser } from '../user'; +import { IAccessRepository, IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories'; +import { mapUser } from '../user'; +import { PartnerResponseDto, UpdatePartnerDto } from './partner.dto'; @Injectable() export class PartnerService { - constructor(@Inject(IPartnerRepository) private repository: IPartnerRepository) {} + private access: AccessCore; + constructor( + @Inject(IPartnerRepository) private repository: IPartnerRepository, + @Inject(IAccessRepository) accessRepository: IAccessRepository, + ) { + this.access = AccessCore.create(accessRepository); + } - async create(authUser: AuthUserDto, sharedWithId: string): Promise { + async create(authUser: AuthUserDto, sharedWithId: string): Promise { const partnerId: PartnerIds = { sharedById: authUser.id, sharedWithId }; const exists = await this.repository.get(partnerId); if (exists) { @@ -29,7 +37,7 @@ export class PartnerService { await this.repository.remove(partner); } - async getAll(authUser: AuthUserDto, direction: PartnerDirection): Promise { + async getAll(authUser: AuthUserDto, direction: PartnerDirection): Promise { const partners = await this.repository.getAll(authUser.id); const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; return partners @@ -38,8 +46,22 @@ export class PartnerService { .map((partner) => this.map(partner, direction)); } - private map(partner: PartnerEntity, direction: PartnerDirection): UserResponseDto { + async update(authUser: AuthUserDto, sharedById: string, dto: UpdatePartnerDto): Promise { + await this.access.requirePermission(authUser, Permission.PARTNER_UPDATE, sharedById); + const partnerId: PartnerIds = { sharedById, sharedWithId: authUser.id }; + + const entity = await this.repository.update({ ...partnerId, inTimeline: dto.inTimeline }); + return this.map(entity, PartnerDirection.SharedWith); + } + + private map(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto { // this is opposite to return the non-me user of the "partner" - return mapUser(direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy); + const user = mapUser( + direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy, + ) as PartnerResponseDto; + + user.inTimeline = partner.inTimeline; + + return user; } } diff --git a/server/src/domain/repositories/access.repository.ts b/server/src/domain/repositories/access.repository.ts index f9ceb6f52..9c009719d 100644 --- a/server/src/domain/repositories/access.repository.ts +++ b/server/src/domain/repositories/access.repository.ts @@ -35,4 +35,8 @@ export interface IAccessRepository { person: { hasOwnerAccess(userId: string, personId: string): Promise; }; + + partner: { + hasUpdateAccess(userId: string, partnerId: string): Promise; + }; } diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts index da8f8547e..2ceabad35 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/domain/repositories/asset.repository.ts @@ -65,7 +65,7 @@ export interface TimeBucketOptions { isTrashed?: boolean; albumId?: string; personId?: string; - userId?: string; + userIds?: string[]; withStacked?: boolean; } diff --git a/server/src/domain/repositories/partner.repository.ts b/server/src/domain/repositories/partner.repository.ts index fd436932c..f0409b67a 100644 --- a/server/src/domain/repositories/partner.repository.ts +++ b/server/src/domain/repositories/partner.repository.ts @@ -17,4 +17,5 @@ export interface IPartnerRepository { get(partner: PartnerIds): Promise; create(partner: PartnerIds): Promise; remove(entity: PartnerEntity): Promise; + update(entity: Partial): Promise; } diff --git a/server/src/immich/controllers/partner.controller.ts b/server/src/immich/controllers/partner.controller.ts index 0ecec54cd..5f9f004f9 100644 --- a/server/src/immich/controllers/partner.controller.ts +++ b/server/src/immich/controllers/partner.controller.ts @@ -1,5 +1,6 @@ -import { AuthUserDto, PartnerDirection, PartnerService, UserResponseDto } from '@app/domain'; -import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common'; +import { AuthUserDto, PartnerDirection, PartnerService } from '@app/domain'; +import { PartnerResponseDto, UpdatePartnerDto } from '@app/domain/partner/partner.dto'; +import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; import { AuthUser, Authenticated } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -17,15 +18,24 @@ export class PartnerController { getPartners( @AuthUser() authUser: AuthUserDto, @Query('direction') direction: PartnerDirection, - ): Promise { + ): Promise { return this.service.getAll(authUser, direction); } @Post(':id') - createPartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { + createPartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.create(authUser, id); } + @Put(':id') + updatePartner( + @AuthUser() authUser: AuthUserDto, + @Param() { id }: UUIDParamDto, + @Body() dto: UpdatePartnerDto, + ): Promise { + return this.service.update(authUser, id, dto); + } + @Delete(':id') removePartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(authUser, id); diff --git a/server/src/infra/entities/partner.entity.ts b/server/src/infra/entities/partner.entity.ts index d7be83082..35d32e4c9 100644 --- a/server/src/infra/entities/partner.entity.ts +++ b/server/src/infra/entities/partner.entity.ts @@ -1,4 +1,4 @@ -import { CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; +import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; import { UserEntity } from './user.entity'; @@ -23,4 +23,7 @@ export class PartnerEntity { @UpdateDateColumn({ type: 'timestamptz' }) updatedAt!: Date; + + @Column({ type: 'boolean', default: false }) + inTimeline!: boolean; } diff --git a/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts b/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts new file mode 100644 index 000000000..59c2f229e --- /dev/null +++ b/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AdddInTimelineToPartnersTable1699562570201 implements MigrationInterface { + name = 'AdddInTimelineToPartnersTable1699562570201' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "partners" ADD "inTimeline" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "partners" DROP COLUMN "inTimeline"`); + } + +} diff --git a/server/src/infra/repositories/access.repository.ts b/server/src/infra/repositories/access.repository.ts index aff498ac3..fa5862885 100644 --- a/server/src/infra/repositories/access.repository.ts +++ b/server/src/infra/repositories/access.repository.ts @@ -249,4 +249,15 @@ export class AccessRepository implements IAccessRepository { }); }, }; + + partner = { + hasUpdateAccess: (userId: string, partnerId: string): Promise => { + return this.partnerRepository.exist({ + where: { + sharedById: partnerId, + sharedWithId: userId, + }, + }); + }, + }; } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 5112fc9f6..3f0b439a3 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -519,7 +519,7 @@ export class AssetRepository implements IAssetRepository { } private getBuilder(options: TimeBucketOptions) { - const { isArchived, isFavorite, isTrashed, albumId, personId, userId, withStacked } = options; + const { isArchived, isFavorite, isTrashed, albumId, personId, userIds, withStacked } = options; let builder = this.repository .createQueryBuilder('asset') @@ -532,11 +532,11 @@ export class AssetRepository implements IAssetRepository { builder = builder.leftJoin('asset.albums', 'album').andWhere('album.id = :albumId', { albumId }); } - if (userId) { - builder = builder.andWhere('asset.ownerId = :userId', { userId }); + if (userIds) { + builder = builder.andWhere('asset.ownerId IN (:...userIds )', { userIds }); } - if (isArchived != undefined) { + if (isArchived !== undefined) { builder = builder.andWhere('asset.isArchived = :isArchived', { isArchived }); } diff --git a/server/src/infra/repositories/partner.repository.ts b/server/src/infra/repositories/partner.repository.ts index c56d8b075..b5b876558 100644 --- a/server/src/infra/repositories/partner.repository.ts +++ b/server/src/infra/repositories/partner.repository.ts @@ -1,7 +1,7 @@ import { IPartnerRepository, PartnerIds } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { DeepPartial, Repository } from 'typeorm'; import { PartnerEntity } from '../entities'; @Injectable() @@ -16,12 +16,22 @@ export class PartnerRepository implements IPartnerRepository { return this.repository.findOne({ where: { sharedById, sharedWithId } }); } - async create({ sharedById, sharedWithId }: PartnerIds): Promise { - await this.repository.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } }); - return this.repository.findOneOrFail({ where: { sharedById, sharedWithId } }); + create({ sharedById, sharedWithId }: PartnerIds): Promise { + return this.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } }); } async remove(entity: PartnerEntity): Promise { await this.repository.remove(entity); } + + update(entity: Partial): Promise { + return this.save(entity); + } + + private async save(entity: DeepPartial): Promise { + await this.repository.save(entity); + return this.repository.findOneOrFail({ + where: { sharedById: entity.sharedById, sharedWithId: entity.sharedWithId }, + }); + } } diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index 7736f085f..70be784c7 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -584,6 +584,52 @@ describe(`${AssetController.name} (e2e)`, () => { ]), ); }); + + it('should return error if time bucket is requested with partners asset and archived', async () => { + const req1 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isArchived: true }); + + expect(req1.status).toBe(400); + expect(req1.body).toEqual(errorStub.badRequest()); + + const req2 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isArchived: undefined }); + + expect(req2.status).toBe(400); + expect(req2.body).toEqual(errorStub.badRequest()); + }); + + it('should return error if time bucket is requested with partners asset and favorite', async () => { + const req1 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isFavorite: true }); + + expect(req1.status).toBe(400); + expect(req1.body).toEqual(errorStub.badRequest()); + + const req2 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isFavorite: false }); + + expect(req2.status).toBe(400); + expect(req2.body).toEqual(errorStub.badRequest()); + }); + + it('should return error if time bucket is requested with partners asset and trash', async () => { + const req = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isTrashed: true }); + + expect(req.status).toBe(400); + expect(req.body).toEqual(errorStub.badRequest()); + }); }); describe('GET /asset/map-marker', () => { diff --git a/server/test/e2e/partner.e2e-spec.ts b/server/test/e2e/partner.e2e-spec.ts index 82a09dcc8..f6b8e144f 100644 --- a/server/test/e2e/partner.e2e-spec.ts +++ b/server/test/e2e/partner.e2e-spec.ts @@ -115,6 +115,26 @@ describe(`${PartnerController.name} (e2e)`, () => { }); }); + describe('PUT /partner/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(server).put(`/partner/${user2.userId}`); + + expect(status).toBe(401); + expect(body).toEqual(errorStub.unauthorized); + }); + + it('should update partner', async () => { + await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId }); + const { status, body } = await request(server) + .put(`/partner/${user2.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ inTimeline: false }); + + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false })); + }); + }); + describe('DELETE /partner/:id', () => { it('should require authentication', async () => { const { status, body } = await request(server).delete(`/partner/${user2.userId}`); diff --git a/server/test/fixtures/partner.stub.ts b/server/test/fixtures/partner.stub.ts index dc54332ea..05c1c67d6 100644 --- a/server/test/fixtures/partner.stub.ts +++ b/server/test/fixtures/partner.stub.ts @@ -9,6 +9,7 @@ export const partnerStub = { sharedBy: userStub.admin, sharedWith: userStub.user1, sharedWithId: userStub.user1.id, + inTimeline: true, }), user1ToAdmin1: Object.freeze({ createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -17,5 +18,6 @@ export const partnerStub = { sharedById: userStub.user1.id, sharedWithId: userStub.admin.id, sharedWith: userStub.admin, + inTimeline: true, }), }; diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 6abfc7c9e..8f1e9355d 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -8,6 +8,7 @@ export interface IAccessRepositoryMock { library: jest.Mocked; timeline: jest.Mocked; person: jest.Mocked; + partner: jest.Mocked; } export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => { @@ -50,5 +51,9 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => person: { hasOwnerAccess: jest.fn(), }, + + partner: { + hasUpdateAccess: jest.fn(), + }, }; }; diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts index a283325d2..1e839ae4f 100644 --- a/server/test/repositories/partner.repository.mock.ts +++ b/server/test/repositories/partner.repository.mock.ts @@ -6,5 +6,6 @@ export const newPartnerRepositoryMock = (): jest.Mocked => { remove: jest.fn(), getAll: jest.fn(), get: jest.fn(), + update: jest.fn(), }; }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 798d388d7..496fa5441 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2361,6 +2361,103 @@ export interface OAuthConfigResponseDto { */ 'url'?: string; } +/** + * + * @export + * @interface PartnerResponseDto + */ +export interface PartnerResponseDto { + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'deletedAt': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'email': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'externalPath': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'id': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'inTimeline'?: boolean; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'storageLabel': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'updatedAt': string; +} /** * * @export @@ -4220,6 +4317,19 @@ export interface UpdateLibraryDto { */ 'name'?: string; } +/** + * + * @export + * @interface UpdatePartnerDto + */ +export interface UpdatePartnerDto { + /** + * + * @type {boolean} + * @memberof UpdatePartnerDto + */ + 'inTimeline': boolean; +} /** * * @export @@ -7274,11 +7384,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBucket', 'size', size) // verify required parameter 'timeBucket' is not null or undefined @@ -7336,6 +7447,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (timeBucket !== undefined) { localVarQueryParameter['timeBucket'] = timeBucket; } @@ -7365,11 +7480,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBuckets', 'size', size) const localVarPath = `/asset/time-buckets`; @@ -7425,6 +7541,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (key !== undefined) { localVarQueryParameter['key'] = key; } @@ -8227,12 +8347,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8245,12 +8366,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8547,7 +8669,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * @@ -8556,7 +8678,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * Get all asset of a device that are in the database, ID only. @@ -9043,6 +9165,13 @@ export interface AssetApiGetTimeBucketRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBucket + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9113,6 +9242,13 @@ export interface AssetApiGetTimeBucketsRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9592,7 +9728,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9603,7 +9739,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -12312,6 +12448,54 @@ export const PartnerApiAxiosParamCreator = function (configuration?: Configurati let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner: async (id: string, updatePartnerDto: UpdatePartnerDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updatePartner', 'id', id) + // verify required parameter 'updatePartnerDto' is not null or undefined + assertParamExists('updatePartner', 'updatePartnerDto', updatePartnerDto) + const localVarPath = `/partner/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updatePartnerDto, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -12333,7 +12517,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.createPartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12343,7 +12527,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { const localVarAxiosArgs = await localVarAxiosParamCreator.getPartners(direction, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12357,6 +12541,17 @@ export const PartnerApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.removePartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updatePartner(id: string, updatePartnerDto: UpdatePartnerDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updatePartner(id, updatePartnerDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -12373,7 +12568,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createPartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, /** @@ -12382,7 +12577,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { + getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPartners(requestParameters.direction, options).then((request) => request(axios, basePath)); }, /** @@ -12394,6 +12589,15 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.removePartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(axios, basePath)); + }, }; }; @@ -12439,6 +12643,27 @@ export interface PartnerApiRemovePartnerRequest { readonly id: string } +/** + * Request parameters for updatePartner operation in PartnerApi. + * @export + * @interface PartnerApiUpdatePartnerRequest + */ +export interface PartnerApiUpdatePartnerRequest { + /** + * + * @type {string} + * @memberof PartnerApiUpdatePartner + */ + readonly id: string + + /** + * + * @type {UpdatePartnerDto} + * @memberof PartnerApiUpdatePartner + */ + readonly updatePartnerDto: UpdatePartnerDto +} + /** * PartnerApi - object-oriented interface * @export @@ -12478,6 +12703,17 @@ export class PartnerApi extends BaseAPI { public removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig) { return PartnerApiFp(this.configuration).removePartner(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PartnerApi + */ + public updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig) { + return PartnerApiFp(this.configuration).updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 2333a55f0..5b10ab1e8 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -97,8 +97,12 @@

- {#if $isMultiSelectState} - assetInteractionStore.clearMultiselect()}> + {#if $isMultiSelectState && user} + assetInteractionStore.clearMultiselect()} + > {#if sharedLink.allowDownload} diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte index dff6f908a..a72bc106a 100644 --- a/web/src/lib/components/photos-page/actions/archive-action.svelte +++ b/web/src/lib/components/photos-page/actions/archive-action.svelte @@ -20,14 +20,14 @@ let loading = false; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleArchive = async () => { const isArchived = !unarchive; loading = true; try { - const assets = Array.from(getAssets()).filter((asset) => asset.isArchived !== isArchived); + const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isArchived !== isArchived); const ids = assets.map(({ id }) => id); if (ids.length > 0) { diff --git a/web/src/lib/components/photos-page/actions/asset-job-actions.svelte b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte index 3e678cdf0..296197a71 100644 --- a/web/src/lib/components/photos-page/actions/asset-job-actions.svelte +++ b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte @@ -14,13 +14,13 @@ AssetJobName.TranscodeVideo, ]; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); - $: isAllVideos = Array.from(getAssets()).every((asset) => asset.type === AssetTypeEnum.Video); + $: isAllVideos = Array.from(getOwnedAssets()).every((asset) => asset.type === AssetTypeEnum.Video); const handleRunJob = async (name: AssetJobName) => { try { - const ids = Array.from(getAssets()).map(({ id }) => id); + const ids = Array.from(getOwnedAssets()).map(({ id }) => id); await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } }); notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info }); clearSelect(); diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte index c5461b2e0..68d133ae9 100644 --- a/web/src/lib/components/photos-page/actions/delete-assets.svelte +++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte @@ -17,7 +17,7 @@ export let menuItem = false; export let force = !$featureFlags.trash; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const dispatch = createEventDispatcher(); @@ -37,7 +37,7 @@ loading = true; try { - const ids = Array.from(getAssets()) + const ids = Array.from(getOwnedAssets()) .filter((a) => !a.isExternal) .map((a) => a.id); await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } }); @@ -75,7 +75,7 @@ {#if isShowConfirmation} (isShowConfirmation = false)} @@ -84,8 +84,8 @@

Are you sure you want to permanently delete - {#if getAssets().size > 1} - these {getAssets().size} assets? This will also remove them from their album(s). + {#if getOwnedAssets().size > 1} + these {getOwnedAssets().size} assets? This will also remove them from their album(s). {:else} this asset? This will also remove it from its album(s). {/if} diff --git a/web/src/lib/components/photos-page/actions/favorite-action.svelte b/web/src/lib/components/photos-page/actions/favorite-action.svelte index 218a75d31..b246670f6 100644 --- a/web/src/lib/components/photos-page/actions/favorite-action.svelte +++ b/web/src/lib/components/photos-page/actions/favorite-action.svelte @@ -20,14 +20,15 @@ let loading = false; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleFavorite = async () => { const isFavorite = !removeFavorite; loading = true; try { - const assets = Array.from(getAssets()).filter((asset) => asset.isFavorite !== isFavorite); + const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isFavorite !== isFavorite); + const ids = assets.map(({ id }) => id); if (ids.length > 0) { diff --git a/web/src/lib/components/photos-page/actions/stack-action.svelte b/web/src/lib/components/photos-page/actions/stack-action.svelte index 876379756..c622998fe 100644 --- a/web/src/lib/components/photos-page/actions/stack-action.svelte +++ b/web/src/lib/components/photos-page/actions/stack-action.svelte @@ -10,11 +10,11 @@ export let onStack: OnStack | undefined = undefined; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleStack = async () => { try { - const assets = Array.from(getAssets()); + const assets = Array.from(getOwnedAssets()); const parent = assets.at(0); if (parent == undefined) { diff --git a/web/src/lib/components/photos-page/asset-select-control-bar.svelte b/web/src/lib/components/photos-page/asset-select-control-bar.svelte index bc6fb823c..5ce4a9ebd 100644 --- a/web/src/lib/components/photos-page/asset-select-control-bar.svelte +++ b/web/src/lib/components/photos-page/asset-select-control-bar.svelte @@ -9,7 +9,8 @@ export interface AssetControlContext { // Wrap assets in a function, because context isn't reactive. - getAssets: () => Set; + getAssets: () => Set; // All assets includes partners' assets + getOwnedAssets: () => Set; // Only assets owned by the user clearSelect: () => void; } @@ -25,8 +26,14 @@ export let assets: Set; export let clearSelect: () => void; + export let ownerId: string | undefined = undefined; - setContext({ getAssets: () => assets, clearSelect }); + setContext({ + getAssets: () => assets, + getOwnedAssets: () => + ownerId !== undefined ? new Set(Array.from(assets).filter((asset) => asset.ownerId === ownerId)) : assets, + clearSelect, + }); diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte index aabe911ef..ee5fe8a09 100644 --- a/web/src/lib/components/user-settings-page/partner-settings.svelte +++ b/web/src/lib/components/user-settings-page/partner-settings.svelte @@ -1,22 +1,71 @@

{#if partners.length > 0} -
- {#each partners as partner (partner.id)} -
- -
-

- {partner.firstName} - {partner.lastName} -

-

- {partner.email} -

+ {#each partners as partner (partner.user.id)} +
+
+
+ +
+

+ {partner.user.firstName} + {partner.user.lastName} +

+

+ {partner.user.email} +

+
- (removePartner = partner)} - icon={mdiClose} - size={'16'} - title="Remove partner" - /> + + {#if partner.sharedByMe} + (removePartner = partner.user)} + icon={mdiClose} + size={'16'} + title="Stop sharing your photos with this user" + /> + {/if}
- {/each} -
+ +
+ + {#if partner.sharedByMe} +
+

SHARED WITH {partner.user.firstName.toUpperCase()}

+

{partner.user.firstName} can access

+
    +
  • + All your photos and videos except those in Archived and Deleted +
  • +
  • + The location where your photos were taken +
  • +
+ {/if} + + + {#if partner.sharedWithMe} +
+

PHOTOS FROM {partner.user.firstName.toUpperCase()}

+ handleShowOnTimelineChanged(partner, detail)} + /> + {/if} +
+
+ {/each} {/if} -
+ +
diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte index 3a90da0b3..6bce7ac0e 100644 --- a/web/src/lib/components/user-settings-page/user-settings-list.svelte +++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte @@ -18,7 +18,6 @@ export let keys: APIKeyResponseDto[] = []; export let devices: AuthDeviceResponseDto[] = []; - export let partners: UserResponseDto[] = []; let oauthOpen = false; if (browser) { @@ -61,7 +60,7 @@ - + diff --git a/web/src/routes/(user)/partners/[userId]/+page.svelte b/web/src/routes/(user)/partners/[userId]/+page.svelte index a52303d83..9beec5b8d 100644 --- a/web/src/routes/(user)/partners/[userId]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/+page.svelte @@ -16,7 +16,7 @@ export let data: PageData; - const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false }); + const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index fb0f9f969..7515d747b 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -26,7 +26,7 @@ let { isViewing: showAssetViewer } = assetViewingStore; let handleEscapeKey = false; - const assetStore = new AssetStore({ isArchived: false, withStacked: true }); + const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; @@ -48,7 +48,11 @@ {#if $isMultiSelectState} - assetInteractionStore.clearMultiselect()}> + assetInteractionStore.clearMultiselect()} + > (handleEscapeKey = true)} /> diff --git a/web/src/routes/(user)/user-settings/+page.server.ts b/web/src/routes/(user)/user-settings/+page.server.ts index a6aa5e15a..346582638 100644 --- a/web/src/routes/(user)/user-settings/+page.server.ts +++ b/web/src/routes/(user)/user-settings/+page.server.ts @@ -10,13 +10,11 @@ export const load = (async ({ parent, locals }) => { const { data: keys } = await locals.api.keyApi.getApiKeys(); const { data: devices } = await locals.api.authenticationApi.getAuthDevices(); - const { data: partners } = await locals.api.partnerApi.getPartners({ direction: 'shared-by' }); return { user, keys, devices, - partners, meta: { title: 'Settings', }, diff --git a/web/src/routes/(user)/user-settings/+page.svelte b/web/src/routes/(user)/user-settings/+page.svelte index d63a28e90..32d95a18d 100644 --- a/web/src/routes/(user)/user-settings/+page.svelte +++ b/web/src/routes/(user)/user-settings/+page.svelte @@ -9,7 +9,7 @@
- +