diff options
| author | Mar0xy <marie@kaifa.ch> | 2023-12-04 02:10:51 +0100 |
|---|---|---|
| committer | Mar0xy <marie@kaifa.ch> | 2023-12-04 02:10:51 +0100 |
| commit | 2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310 (patch) | |
| tree | aa9801e261ed978d553cfc8cd80fee524d6496a6 /packages/backend/src/server/api/endpoints/notes | |
| parent | upd: add additional check to visibility selector for boost (diff) | |
| download | sharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.tar.gz sharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.tar.bz2 sharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.zip | |
add: Bubble timeline
Closes transfem-org/Sharkey#154
Diffstat (limited to 'packages/backend/src/server/api/endpoints/notes')
| -rw-r--r-- | packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts new file mode 100644 index 0000000000..0652c82a9d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts @@ -0,0 +1,130 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import type { NotesRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '../../error.js'; +import { CacheService } from '@/core/CacheService.js'; +import { MetaService } from '@/core/MetaService.js'; + +export const meta = { + tags: ['notes'], + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, + + errors: { + btlDisabled: { + message: 'Bubble timeline has been disabled.', + code: 'BTL_DISABLED', + id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6c', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + withFiles: { type: 'boolean', default: false }, + withBots: { type: 'boolean', default: true }, + withRenotes: { type: 'boolean', default: true }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private roleService: RoleService, + private activeUsersChart: ActiveUsersChart, + private cacheService: CacheService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const policies = await this.roleService.getUserPolicies(me ? me.id : null); + const instance = await this.metaService.fetch(); + if (!policies.btlAvailable) { + throw new ApiError(meta.errors.btlDisabled); + } + + const [ + followings, + ] = me ? await Promise.all([ + this.cacheService.userFollowingsCache.fetch(me.id), + ]) : [undefined]; + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.visibility = \'public\'') + .andWhere('note.channelId IS NULL') + .andWhere( + `(note.userHost = ANY ('{"${instance.bubbleInstances.join('","')}"}'))`, + ) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + if (me) { + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + + if (!ps.withBots) query.andWhere('user.isBot = FALSE'); + + if (ps.withRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.where('note.renoteId IS NULL'); + qb.orWhere(new Brackets(qb => { + qb.where('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + })); + })); + } + //#endregion + + let timeline = await query.limit(ps.limit).getMany(); + + timeline = timeline.filter(note => { + if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false; + return true; + }); + + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} |