summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-06-01 20:52:12 +0000
committerHazelnoot <acomputerdog@gmail.com>2025-06-01 20:52:12 +0000
commit89a32041aa9e7d7c438fb483de3fa0191621b8a3 (patch)
tree16515b1838284ff319a4aed26115900c66040fba /packages/backend/src/server/api
parentmerge: Use secureResolve for Actor collections (resolves #1087) (!1087) (diff)
parentcheck permission in frontend before display trending polls (diff)
downloadsharkey-89a32041aa9e7d7c438fb483de3fa0191621b8a3.tar.gz
sharkey-89a32041aa9e7d7c438fb483de3fa0191621b8a3.tar.bz2
sharkey-89a32041aa9e7d7c438fb483de3fa0191621b8a3.zip
merge: Overhaul trending polls (!1022)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1022 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts94
1 files changed, 75 insertions, 19 deletions
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
index 33a9c281b3..6f96821a63 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -9,13 +9,13 @@ import type { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepo
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
+import { QueryService } from '@/core/QueryService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['notes'],
- requireCredential: true,
- kind: 'read:account',
-
res: {
type: 'array',
optional: false, nullable: false,
@@ -26,10 +26,24 @@ export const meta = {
},
},
- // 2 calls per second
+ errors: {
+ ltlDisabled: {
+ message: 'Local timeline has been disabled.',
+ code: 'LTL_DISABLED',
+ id: '45a6eb02-7695-4393-b023-dd3be9aaaefd',
+ },
+ gtlDisabled: {
+ message: 'Global timeline has been disabled.',
+ code: 'GTL_DISABLED',
+ id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b',
+ },
+ },
+
+ // Up to 10 calls, then 2 per second
limit: {
- duration: 1000,
- max: 2,
+ type: 'bucket',
+ size: 10,
+ dripRate: 500,
},
} as const;
@@ -39,6 +53,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
excludeChannels: { type: 'boolean', default: false },
+ local: { type: 'boolean', nullable: true, default: null },
+ expired: { type: 'boolean', default: false },
},
required: [],
} as const;
@@ -59,18 +75,54 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private mutingsRepository: MutingsRepository,
private noteEntityService: NoteEntityService,
+ private readonly queryService: QueryService,
+ private readonly roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.pollsRepository.createQueryBuilder('poll')
- .where('poll.userHost IS NULL')
- .andWhere('poll.userId != :meId', { meId: me.id })
- .andWhere('poll.noteVisibility = \'public\'')
- .andWhere(new Brackets(qb => {
+ .innerJoinAndSelect('poll.note', 'note')
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .andWhere('user.isExplorable = TRUE')
+ ;
+
+ if (me) {
+ query.andWhere('poll.userId != :meId', { meId: me.id });
+ }
+
+ if (ps.expired) {
+ query.andWhere('poll.expiresAt IS NOT NULL');
+ query.andWhere('poll.expiresAt <= :expiresMax', {
+ expiresMax: new Date(),
+ });
+ query.andWhere('poll.expiresAt >= :expiresMin', {
+ expiresMin: new Date(Date.now() - (1000 * 60 * 60 * 24 * 7)),
+ });
+ } else {
+ query.andWhere(new Brackets(qb => {
qb
.where('poll.expiresAt IS NULL')
.orWhere('poll.expiresAt > :now', { now: new Date() });
}));
+ }
+
+ const policies = await this.roleService.getUserPolicies(me?.id ?? null);
+ if (ps.local != null) {
+ if (ps.local) {
+ if (!policies.ltlAvailable) throw new ApiError(meta.errors.ltlDisabled);
+ query.andWhere('poll.userHost IS NULL');
+ } else {
+ if (!policies.gtlAvailable) throw new ApiError(meta.errors.gtlDisabled);
+ query.andWhere('poll.userHost IS NOT NULL');
+ }
+ } else {
+ if (!policies.gtlAvailable) throw new ApiError(meta.errors.gtlDisabled);
+ }
+ /*
//#region exclude arleady voted polls
const votedQuery = this.pollVotesRepository.createQueryBuilder('vote')
.select('vote.noteId')
@@ -81,16 +133,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
query.setParameters(votedQuery.getParameters());
//#endregion
+ */
- //#region mute
- const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: me.id });
-
- query
- .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`);
-
- query.setParameters(mutingQuery.getParameters());
+ //#region block/mute/vis
+ this.queryService.generateVisibilityQuery(query, me);
+ this.queryService.generateBlockedHostQueryForNote(query);
+ if (me) {
+ this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateMutedUserQueryForNotes(query, me);
+ }
//#endregion
//#region exclude channels
@@ -107,6 +158,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (polls.length === 0) return [];
+ /*
const notes = await this.notesRepository.find({
where: {
id: In(polls.map(poll => poll.noteId)),
@@ -115,6 +167,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: 'DESC',
},
});
+ */
+
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const notes = polls.map(poll => poll.note!);
return await this.noteEntityService.packMany(notes, me, {
detail: true,