diff --git a/src/app/guarantees/pages/guarantee-instance/guarantee-instance.component.ts b/src/app/guarantees/pages/guarantee-instance/guarantee-instance.component.ts index 0cb1741..e38cb8c 100644 --- a/src/app/guarantees/pages/guarantee-instance/guarantee-instance.component.ts +++ b/src/app/guarantees/pages/guarantee-instance/guarantee-instance.component.ts @@ -53,12 +53,11 @@ export class GuaranteeInstanceComponent implements OnInit { this.cachedApi.getGuaranteesByInstance(currentInstance, {clear: true}), this.cachedApi.getCensuresByInstances([currentInstance], {clear: true}), this.cachedApi.getHesitationsByInstances([currentInstance], {clear: true}), - this.cachedApi.getWhitelistedInstances({clear: true}), ]).subscribe(() => { this.loading = false; this.router.navigateByUrl('/guarantees/my').then(() => { this.messageService.createSuccess(`${this.form.controls.instance.value} was successfully guaranteed!`); - this.cachedApi.getWhitelistedInstances({clear: true}).subscribe(); + this.cachedApi.clearWhitelistCache(); }); }); }); diff --git a/src/app/guarantees/pages/my-guarantees/my-guarantees.component.ts b/src/app/guarantees/pages/my-guarantees/my-guarantees.component.ts index 0b01f36..df7fc09 100644 --- a/src/app/guarantees/pages/my-guarantees/my-guarantees.component.ts +++ b/src/app/guarantees/pages/my-guarantees/my-guarantees.component.ts @@ -65,7 +65,7 @@ export class MyGuaranteesComponent implements OnInit { this.instancesGuaranteedByMe = this.instancesGuaranteedByMe.filter( guaranteedInstance => guaranteedInstance.domain !== instance, ); - this.cachedApi.getWhitelistedInstances({clear: true}).subscribe(); + this.cachedApi.clearWhitelistCache(); this.cachedApi.getGuaranteesByInstance(this.instance.name).subscribe(); }); } diff --git a/src/app/instances/pages/edit-own-instance/edit-own-instance.component.html b/src/app/instances/pages/edit-own-instance/edit-own-instance.component.html index ca3c8c5..3900939 100644 --- a/src/app/instances/pages/edit-own-instance/edit-own-instance.component.html +++ b/src/app/instances/pages/edit-own-instance/edit-own-instance.component.html @@ -16,6 +16,13 @@

Instance details

+
+ + + +
diff --git a/src/app/instances/pages/edit-own-instance/edit-own-instance.component.ts b/src/app/instances/pages/edit-own-instance/edit-own-instance.component.ts index bde5f1f..4597440 100644 --- a/src/app/instances/pages/edit-own-instance/edit-own-instance.component.ts +++ b/src/app/instances/pages/edit-own-instance/edit-own-instance.component.ts @@ -1,12 +1,13 @@ import {Component, OnInit} from '@angular/core'; import {TitleService} from "../../../services/title.service"; -import {FediseerApiService} from "../../../services/fediseer-api.service"; +import {ApiResponse, FediseerApiService} from "../../../services/fediseer-api.service"; import {toPromise} from "../../../types/resolvable"; import {ApiResponseHelperService} from "../../../services/api-response-helper.service"; import {FormControl, FormGroup, Validators} from "@angular/forms"; import {InstanceDetailResponse} from "../../../response/instance-detail.response"; import {MessageService} from "../../../services/message.service"; import {ListVisibility} from "../../../types/list-visibility"; +import {CachedFediseerApiService} from "../../../services/cached-fediseer-api.service"; @Component({ selector: 'app-edit-own-instance', @@ -16,6 +17,8 @@ import {ListVisibility} from "../../../types/list-visibility"; export class EditOwnInstanceComponent implements OnInit { protected readonly ListVisibility = ListVisibility; + private originalTags: string[] = []; + public loading: boolean = true; public detail: InstanceDetailResponse | null = null; public form = new FormGroup({ @@ -24,11 +27,14 @@ export class EditOwnInstanceComponent implements OnInit { censuresVisibility: new FormControl(ListVisibility.Open, [Validators.required]), hesitationsVisibility: new FormControl(ListVisibility.Open, [Validators.required]), endorsementsVisibility: new FormControl(ListVisibility.Open, [Validators.required]), + tags: new FormControl([]), }); + public availableTags: string[] = []; constructor( private readonly titleService: TitleService, private readonly api: FediseerApiService, + private readonly cachedApi: CachedFediseerApiService, private readonly apiResponseHelper: ApiResponseHelperService, private readonly messageService: MessageService, ) { @@ -49,23 +55,45 @@ export class EditOwnInstanceComponent implements OnInit { censuresVisibility: this.detail.visibility_censures!, endorsementsVisibility: this.detail.visibility_endorsements!, hesitationsVisibility: this.detail.visibility_hesitations!, + tags: this.detail.tags, }); this.titleService.title = `Editing ${this.detail.domain}`; + this.originalTags = this.detail.tags; + // this.availableTags = this.detail.tags; + this.availableTags = await toPromise(this.cachedApi.getAvailableTags()); this.loading = false; } public async updateInstance(): Promise { - const response = await toPromise(this.api.updateInstanceData(this.detail!.domain, { - moderators: this.form.controls.moderators.value, - sysadmins: this.form.controls.sysadmins.value, - visibility_endorsements: this.form.controls.endorsementsVisibility.value!, - visibility_censures: this.form.controls.censuresVisibility.value!, - visibility_hesitations: this.form.controls.hesitationsVisibility.value!, - })); - if (this.apiResponseHelper.handleErrors([response])) { + const addedTags = this.form.controls.tags.value!.filter(tag => !this.originalTags.includes(tag)); + const removedTags = this.originalTags.filter(tag => !this.form.controls.tags.value!.includes(tag)); + + const requests: Promise>[] = [ + toPromise(this.api.updateInstanceData(this.detail!.domain, { + moderators: this.form.controls.moderators.value, + sysadmins: this.form.controls.sysadmins.value, + visibility_endorsements: this.form.controls.endorsementsVisibility.value!, + visibility_censures: this.form.controls.censuresVisibility.value!, + visibility_hesitations: this.form.controls.hesitationsVisibility.value!, + })), + ]; + if (addedTags.length) { + requests.push(toPromise(this.api.tagInstance(addedTags))); + } + if (removedTags.length) { + requests.push(toPromise(this.api.removeInstanceTags(removedTags))); + } + + const responses = await Promise.all(requests); + + if (this.apiResponseHelper.handleErrors(responses)) { return; } + this.cachedApi.clearWhitelistCache(); + this.cachedApi.getAvailableTags({clear: true}).subscribe(); + this.cachedApi.getCurrentInstanceInfo(null, {clear: true}).subscribe(); + this.messageService.createSuccess('The data were successfully updated!') } } diff --git a/src/app/instances/pages/instance-detail/instance-detail.component.html b/src/app/instances/pages/instance-detail/instance-detail.component.html index ae1ef46..f432632 100644 --- a/src/app/instances/pages/instance-detail/instance-detail.component.html +++ b/src/app/instances/pages/instance-detail/instance-detail.component.html @@ -52,6 +52,20 @@

Instance details

+ + + Tags + + + + N/A + + + {{tag}} + + + + Open registrations? @@ -145,14 +159,6 @@

Instance details

{{'app.list_visibility.private' | transloco}} - - - Instance status - - - - -
diff --git a/src/app/instances/pages/instance-detail/instance-detail.component.scss b/src/app/instances/pages/instance-detail/instance-detail.component.scss index 5f5d12d..5de954f 100644 --- a/src/app/instances/pages/instance-detail/instance-detail.component.scss +++ b/src/app/instances/pages/instance-detail/instance-detail.component.scss @@ -1,3 +1,7 @@ :host { min-width: 100%; } + +.btn-sm { + margin-right: 5px; +} diff --git a/src/app/instances/pages/instance-detail/instance-detail.component.ts b/src/app/instances/pages/instance-detail/instance-detail.component.ts index ccdc033..00427bb 100644 --- a/src/app/instances/pages/instance-detail/instance-detail.component.ts +++ b/src/app/instances/pages/instance-detail/instance-detail.component.ts @@ -11,6 +11,7 @@ import {NormalizedInstanceDetailResponse} from "../../../response/normalized-ins import {CachedFediseerApiService} from "../../../services/cached-fediseer-api.service"; import {ListVisibility} from "../../../types/list-visibility"; import {InstanceMoveEvent} from "../../../shared/components/instance-move-to-list/instance-move-to-list.component"; +import {InstanceFlag} from "../../../types/instance-flag"; @Component({ selector: 'app-instance-detail', @@ -122,4 +123,6 @@ export class InstanceDetailComponent implements OnInit { public async onInstanceMoved(event: InstanceMoveEvent) { } + + protected readonly InstanceFlag = InstanceFlag; } diff --git a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.html b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.html index 498b95a..3ff5bcf 100644 --- a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.html +++ b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.html @@ -18,6 +18,7 @@ Endorsements Sysadmins / moderators + Tags Instance status Endorsed by me? @@ -50,6 +51,14 @@ {{instance.moderators}}
+ + N/A + + + {{tag}} + + + diff --git a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.scss b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.scss index 5f5d12d..5de954f 100644 --- a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.scss +++ b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.scss @@ -1,3 +1,7 @@ :host { min-width: 100%; } + +.btn-sm { + margin-right: 5px; +} diff --git a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.ts b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.ts index c70e748..3855b94 100644 --- a/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.ts +++ b/src/app/instances/pages/whitelisted-instances/whitelisted-instances.component.ts @@ -10,6 +10,7 @@ import {Instance} from "../../../user/instance"; import {SuccessResponse} from "../../../response/success.response"; import {ApiResponseHelperService} from "../../../services/api-response-helper.service"; import {CachedFediseerApiService} from "../../../services/cached-fediseer-api.service"; +import {WhitelistFilter} from "../../../types/whitelist-filter"; @Component({ selector: 'app-whitelisted-instances', @@ -44,38 +45,44 @@ export class WhitelistedInstancesComponent implements OnInit { public async ngOnInit(): Promise { this.titleService.title = 'Whitelisted instances'; - if (!this.currentInstance.anonymous) { - const response = await toPromise(this.cachedApi.getEndorsementsByInstances([this.currentInstance.name])); + this.activatedRoute.queryParams.subscribe(async queryParams => { + this.loading = true; + this.currentPage = queryParams['page'] ? Number(queryParams['page']) : 1; + + const filters: WhitelistFilter = {}; + if (queryParams['tags']) { + filters.tags = queryParams['tags'].split(','); + } + + if (!this.currentInstance.anonymous) { + const response = await toPromise(this.cachedApi.getEndorsementsByInstances([this.currentInstance.name])); + if (this.apiResponseHelper.handleErrors([response])) { + this.loading = false; + return; + } + this.endorsedByMe = response.successResponse!.instances.map(instance => instance.domain); + } + + const response = await toPromise(this.cachedApi.getWhitelistedInstances(filters)); if (this.apiResponseHelper.handleErrors([response])) { this.loading = false; return; } - this.endorsedByMe = response.successResponse!.instances.map(instance => instance.domain); - } + this.allInstances = response.successResponse!.instances.sort((a, b) => { + if (a.endorsements === b.endorsements) { + return 0; + } - const response = await toPromise(this.cachedApi.getWhitelistedInstances()); - if (this.apiResponseHelper.handleErrors([response])) { - this.loading = false; - return; - } - this.allInstances = response.successResponse!.instances.sort((a, b) => { - if (a.endorsements === b.endorsements) { - return 0; + return a.endorsements > b.endorsements ? -1 : 1; + }); + this.titleService.title = `Whitelisted instances (${this.allInstances.length})`; + this.maxPage = Math.ceil(this.allInstances.length / this.perPage); + for (let i = 1; i <= this.maxPage; ++i) { + this.pages.push(i); } - return a.endorsements > b.endorsements ? -1 : 1; - }); - this.titleService.title = `Whitelisted instances (${this.allInstances.length})`; - this.maxPage = Math.ceil(this.allInstances.length / this.perPage); - for (let i = 1; i <= this.maxPage; ++i) { - this.pages.push(i); - } - - this.loading = false; - - this.activatedRoute.queryParams.subscribe(query => { - this.currentPage = query['page'] ? Number(query['page']) : 1; this.instances = this.allInstances.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage); + this.loading = false; }); } diff --git a/src/app/response/instance-detail.response.ts b/src/app/response/instance-detail.response.ts index 401440c..a9ffe6c 100644 --- a/src/app/response/instance-detail.response.ts +++ b/src/app/response/instance-detail.response.ts @@ -27,4 +27,5 @@ export interface InstanceDetailResponse { visibility_hesitations?: ListVisibility; state: InstanceStatus; flags: {flag: InstanceFlag, comment: string}[]; + tags: string[]; } diff --git a/src/app/services/cached-fediseer-api.service.ts b/src/app/services/cached-fediseer-api.service.ts index 6ae2911..bda51d8 100644 --- a/src/app/services/cached-fediseer-api.service.ts +++ b/src/app/services/cached-fediseer-api.service.ts @@ -1,12 +1,13 @@ import {Injectable} from '@angular/core'; import {ApiResponse, FediseerApiService} from "./fediseer-api.service"; import {RuntimeCacheService} from "./cache/runtime-cache.service"; -import {Observable, of, tap} from "rxjs"; +import {map, Observable, of, tap} from "rxjs"; import {InstanceDetailResponse} from "../response/instance-detail.response"; import {int} from "../types/number"; import {PermanentCacheService} from "./cache/permanent-cache.service"; import {Cache, CacheItem} from "./cache/cache"; import {InstanceListResponse} from "../response/instance-list.response"; +import {WhitelistFilter} from "../types/whitelist-filter"; export enum CacheType { Runtime, @@ -61,21 +62,26 @@ export class CachedFediseerApiService { ); } - public getWhitelistedInstances(cacheConfig: CacheConfiguration = {}): Observable>> { + public getWhitelistedInstances(whitelistFilter: WhitelistFilter = {}, cacheConfig: CacheConfiguration = {}): Observable>> { cacheConfig.type ??= CacheType.Permanent; cacheConfig.ttl ??= 120; - const cacheKey = `api.whitelist${cacheConfig.ttl}`; + const cacheKey = `api.whitelist${cacheConfig.ttl}.${JSON.stringify(whitelistFilter)}`; const item = this.getCacheItem>(cacheKey, cacheConfig)!; if (item.isHit && !cacheConfig.clear) { return this.getSuccessResponse(item); } - return this.api.getWhitelistedInstances().pipe( + return this.api.getWhitelistedInstances(whitelistFilter).pipe( tap(this.storeResponse(item, cacheConfig)), ); } + public clearWhitelistCache(): void { + this.runtimeCache.clearByPrefix('api.whitelist'); + this.permanentCache.clearByPrefix('api.whitelist'); + } + public getHesitationsByInstances(instances: string[], cacheConfig: CacheConfiguration = {}): Observable>> { cacheConfig.type ??= CacheType.Permanent; cacheConfig.ttl ??= 60; @@ -174,6 +180,37 @@ export class CachedFediseerApiService { ); } + public getAvailableTags(cacheConfig: CacheConfiguration = {}): Observable { + cacheConfig.type ??= CacheType.Permanent; + cacheConfig.ttl ??= 300; + + const cacheKey = `api.tags${cacheConfig.ttl}`; + + const item = this.getCacheItem(cacheKey, cacheConfig)!; + + if (item.isHit && !cacheConfig.clear) { + return of(item.value!); + } + + return this.getWhitelistedInstances() + .pipe( + map (response => { + if (!response.success) { + return []; + } + + return response.successResponse!.instances.flatMap(instance => instance.tags); + }), + tap (result => { + item.value = result; + if (cacheConfig.ttl! >= 0) { + item.expiresAt = new Date(new Date().getTime() + (cacheConfig.ttl! * 1_000)); + } + this.saveCacheItem(item, cacheConfig); + }), + ) + } + public clearCache(): void { this.runtimeCache.clear(); this.permanentCache.clear(); diff --git a/src/app/services/fediseer-api.service.ts b/src/app/services/fediseer-api.service.ts index b0dd35f..1fd3fb4 100644 --- a/src/app/services/fediseer-api.service.ts +++ b/src/app/services/fediseer-api.service.ts @@ -18,6 +18,7 @@ import {ResetApiKeyResponse} from "../response/reset-api-key.response"; import {PrivateMessageProxy} from "../types/private-message-proxy"; import {SolicitationInstanceDetailResponse} from "../response/solicitation-instance-detail.response"; import {ActionLogFilter} from "../types/action-log.filter"; +import {WhitelistFilter} from "../types/whitelist-filter"; export interface ApiResponse { success: boolean; @@ -177,15 +178,21 @@ export class FediseerApiService { return this.sendRequest(HttpMethod.Patch, `hesitations/${instance}`, body); } - public getWhitelistedInstances(): Observable>> { + public getWhitelistedInstances(filter: WhitelistFilter = {}): Observable>> { const maxPerPage = 100; // from api + let body: {[key: string]: string} = {}; + if (filter.tags !== undefined) { + body['tags_csv'] = filter.tags.join(','); + } + const sendRequest = (page: number, limit: number): Observable>> => this.sendRequest( HttpMethod.Get, `whitelist`, { page: String(page), limit: String(limit), + ...body, }, ); @@ -380,6 +387,18 @@ export class FediseerApiService { return this.sendRequest(HttpMethod.Post, `solicitations`, body); } + public tagInstance(tags: string[]): Observable> { + return this.sendRequest(HttpMethod.Put, `tags`, { + tags_csv: tags.join(','), + }); + } + + public removeInstanceTags(tags: string[]): Observable> { + return this.sendRequest(HttpMethod.Delete, `tags`, { + tags_csv: tags.join(','), + }); + } + private sendRequest( method: HttpMethod, endpoint: string, diff --git a/src/app/solicitations/pages/list-solicitations/list-solicitations.component.ts b/src/app/solicitations/pages/list-solicitations/list-solicitations.component.ts index 2e9af2a..bd0da43 100644 --- a/src/app/solicitations/pages/list-solicitations/list-solicitations.component.ts +++ b/src/app/solicitations/pages/list-solicitations/list-solicitations.component.ts @@ -60,7 +60,7 @@ export class ListSolicitationsComponent implements OnInit { this.instances = this.instances.filter(item => item.domain !== instance); } this.loading = false; - this.cachedApi.getWhitelistedInstances({clear: true}).subscribe(); + this.cachedApi.clearWhitelistCache(); this.cachedApi.getGuaranteesByInstance(this.authManager.currentInstanceSnapshot.name, {clear: true}).subscribe(); } } diff --git a/src/app/types/whitelist-filter.ts b/src/app/types/whitelist-filter.ts new file mode 100644 index 0000000..5dbfc0a --- /dev/null +++ b/src/app/types/whitelist-filter.ts @@ -0,0 +1,3 @@ +export interface WhitelistFilter { + tags?: string[]; +}