2023.10.0-beta.15-prismisskey.3
This commit is contained in:
parent
1dcc649152
commit
8358ace249
|
@ -17,7 +17,6 @@ import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
|
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import {QueryService} from "@/core/QueryService.js";
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
@ -69,11 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
@Inject(DI.followingsRepository)
|
|
||||||
private followingsRepository: FollowingsRepository,
|
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private queryService: QueryService,
|
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
@ -81,8 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(me.id);
|
const policies = await this.roleService.getUserPolicies(me.id);
|
||||||
if (!policies.ltlAvailable) {
|
if (!policies.ltlAvailable) {
|
||||||
|
@ -107,68 +102,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||||
noteIds = noteIds.slice(0, ps.limit);
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
if (noteIds.length < ps.limit) {
|
|
||||||
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
|
||||||
.select('following.followeeId')
|
|
||||||
.where('following.followerId = :followerId', { followerId: me.id });
|
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
|
||||||
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
|
|
||||||
.andWhere(new Brackets(qb => {
|
|
||||||
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id })
|
|
||||||
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
|
||||||
}))
|
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
|
||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
|
||||||
.setParameters(followingQuery.getParameters());
|
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
|
||||||
query.andWhere(new Brackets(qb => {
|
|
||||||
qb.orWhere('note.userId != :meId', { meId: me.id });
|
|
||||||
qb.orWhere('note.renoteId IS NULL');
|
|
||||||
qb.orWhere('note.text IS NOT NULL');
|
|
||||||
qb.orWhere('note.fileIds != \'{}\'');
|
|
||||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.includeRenotedMyNotes === false) {
|
|
||||||
query.andWhere(new Brackets(qb => {
|
|
||||||
qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
|
|
||||||
qb.orWhere('note.renoteId IS NULL');
|
|
||||||
qb.orWhere('note.text IS NOT NULL');
|
|
||||||
qb.orWhere('note.fileIds != \'{}\'');
|
|
||||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.includeLocalRenotes === false) {
|
|
||||||
query.andWhere(new Brackets(qb => {
|
|
||||||
qb.orWhere('note.renoteUserHost IS NOT NULL');
|
|
||||||
qb.orWhere('note.renoteId IS NULL');
|
|
||||||
qb.orWhere('note.text IS NOT NULL');
|
|
||||||
qb.orWhere('note.fileIds != \'{}\'');
|
|
||||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.withFiles) {
|
|
||||||
query.andWhere('note.fileIds != \'{}\'');
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
const ids = await query.limit(ps.limit - noteIds.length).getMany();
|
|
||||||
noteIds = noteIds.concat(ids.map(note => note.id));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
|
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import {QueryService} from "@/core/QueryService.js";
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
@ -67,13 +66,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private queryService: QueryService,
|
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
|
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
|
||||||
if (!policies.ltlAvailable) {
|
if (!policies.ltlAvailable) {
|
||||||
throw new ApiError(meta.errors.ltlDisabled);
|
throw new ApiError(meta.errors.ltlDisabled);
|
||||||
|
@ -89,56 +90,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.cacheService.userBlockedCache.fetch(me.id),
|
this.cacheService.userBlockedCache.fetch(me.id),
|
||||||
]) : [new Set<string>(), new Set<string>(), new Set<string>()];
|
]) : [new Set<string>(), new Set<string>(), new Set<string>()];
|
||||||
|
|
||||||
let timeline: MiNote[] = [];
|
let noteIds = await this.redisTimelineService.get(ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', untilId, sinceId);
|
||||||
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
|
||||||
let noteIdsRes: [string, string[]][] = [];
|
|
||||||
|
|
||||||
if (!ps.sinceId && !ps.sinceDate) {
|
|
||||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
|
||||||
ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
|
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
|
||||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
|
||||||
'COUNT', limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
let noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
|
||||||
|
|
||||||
if (noteIds.length < limit) {
|
|
||||||
//#region Construct query
|
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
|
||||||
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
|
|
||||||
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
|
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
|
||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
|
||||||
if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
|
||||||
|
|
||||||
if (ps.withFiles) {
|
|
||||||
query.andWhere('note.fileIds != \'{}\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ps.withRenotes === false) {
|
|
||||||
query.andWhere(new Brackets(qb => {
|
|
||||||
qb.orWhere('note.renoteId IS NULL');
|
|
||||||
qb.orWhere(new Brackets(qb => {
|
|
||||||
qb.orWhere('note.text IS NOT NULL');
|
|
||||||
qb.orWhere('note.fileIds != \'{}\'');
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
const ids = await query.limit(limit - noteIds.length).getMany();
|
|
||||||
noteIds = noteIds.concat(ids.map(note => note.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -153,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||||
.leftJoinAndSelect('note.channel', 'channel');
|
.leftJoinAndSelect('note.channel', 'channel');
|
||||||
|
|
||||||
timeline = await query.getMany();
|
let timeline = await query.getMany();
|
||||||
|
|
||||||
timeline = timeline.filter(note => {
|
timeline = timeline.filter(note => {
|
||||||
if (me && (note.userId === me.id)) {
|
if (me && (note.userId === me.id)) {
|
||||||
|
@ -171,6 +124,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: フィルタした結果件数が足りなかった場合の対応
|
||||||
|
|
||||||
timeline.sort((a, b) => a.id > b.id ? -1 : 1);
|
timeline.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||||
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
|
|
|
@ -60,15 +60,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private queryService: QueryService,
|
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
followings,
|
followings,
|
||||||
|
@ -82,39 +81,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.cacheService.userBlockedCache.fetch(me.id),
|
this.cacheService.userBlockedCache.fetch(me.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let timeline: MiNote[] = [];
|
let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
|
||||||
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
|
||||||
let noteIdsRes: [string, string[]][] = [];
|
|
||||||
|
|
||||||
if (!ps.sinceId && !ps.sinceDate) {
|
|
||||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
|
||||||
ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
|
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
|
||||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
|
||||||
'COUNT', limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
let noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
|
||||||
if (noteIds.length < limit) {
|
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
|
||||||
const followingIds = Object.keys(followings);
|
|
||||||
if (followingIds.length > 0) {
|
|
||||||
const meOrFolloweeIds = [me.id, ...followingIds];
|
|
||||||
query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
|
|
||||||
} else {
|
|
||||||
query.andWhere('note.userId = :meId', { meId: me.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
|
||||||
const ids = await query.limit(limit - noteIds.length).getMany();
|
|
||||||
noteIds = noteIds.concat(ids.map(note => note.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -129,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||||
.leftJoinAndSelect('note.channel', 'channel');
|
.leftJoinAndSelect('note.channel', 'channel');
|
||||||
|
|
||||||
timeline = await query.getMany();
|
let timeline = await query.getMany();
|
||||||
|
|
||||||
timeline = timeline.filter(note => {
|
timeline = timeline.filter(note => {
|
||||||
if (note.userId === me.id) {
|
if (note.userId === me.id) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsersRepository, NotesRepository } from '@/models/_.js';
|
import type { UsersRepository, NotesRepository , DriveFilesRepository, MiDriveFile} from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
|
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -14,6 +14,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
|
@ -34,6 +35,11 @@ export const meta = {
|
||||||
code: 'NO_SUCH_NOTE',
|
code: 'NO_SUCH_NOTE',
|
||||||
id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
|
id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
|
||||||
},
|
},
|
||||||
|
noSuchFile: {
|
||||||
|
message: 'Some files are not found.',
|
||||||
|
code: 'NO_SUCH_FILE',
|
||||||
|
id: 'b6992544-63e7-67f0-fa7f-32444b1b5306',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -41,15 +47,68 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
noteId: { type: 'string', format: 'misskey:id' },
|
noteId: { type: 'string', format: 'misskey:id' },
|
||||||
|
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
|
||||||
|
visibleUserIds: { type: 'array', uniqueItems: true, items: {
|
||||||
|
type: 'string', format: 'misskey:id',
|
||||||
|
} },
|
||||||
|
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||||
|
localOnly: { type: 'boolean', default: false },
|
||||||
|
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||||
|
noExtractMentions: { type: 'boolean', default: false },
|
||||||
|
noExtractHashtags: { type: 'boolean', default: false },
|
||||||
|
noExtractEmojis: { type: 'boolean', default: false },
|
||||||
|
replyId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
renoteId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
channelId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
|
||||||
|
// anyOf内にバリデーションを書いても最初の一つしかチェックされない
|
||||||
|
// See https://github.com/misskey-dev/misskey/pull/10082
|
||||||
text: {
|
text: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
minLength: 1,
|
minLength: 1,
|
||||||
maxLength: MAX_NOTE_TEXT_LENGTH,
|
maxLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
nullable: false,
|
nullable: true,
|
||||||
|
},
|
||||||
|
fileIds: {
|
||||||
|
type: 'array',
|
||||||
|
uniqueItems: true,
|
||||||
|
minItems: 1,
|
||||||
|
maxItems: 16,
|
||||||
|
items: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
mediaIds: {
|
||||||
|
type: 'array',
|
||||||
|
uniqueItems: true,
|
||||||
|
minItems: 1,
|
||||||
|
maxItems: 16,
|
||||||
|
items: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
poll: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: true,
|
||||||
|
properties: {
|
||||||
|
choices: {
|
||||||
|
type: 'array',
|
||||||
|
uniqueItems: true,
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 10,
|
||||||
|
items: { type: 'string', minLength: 1, maxLength: 50 },
|
||||||
|
},
|
||||||
|
multiple: { type: 'boolean' },
|
||||||
|
expiresAt: { type: 'integer', nullable: true },
|
||||||
|
expiredAfter: { type: 'integer', nullable: true, minimum: 1 },
|
||||||
|
},
|
||||||
|
required: ['choices'],
|
||||||
},
|
},
|
||||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
|
||||||
},
|
},
|
||||||
required: ['noteId', 'text', 'cw'],
|
// (re)note with text, files and poll are optional
|
||||||
|
anyOf: [
|
||||||
|
{ required: ['text'] },
|
||||||
|
{ required: ['renoteId'] },
|
||||||
|
{ required: ['fileIds'] },
|
||||||
|
{ required: ['mediaIds'] },
|
||||||
|
{ required: ['poll'] },
|
||||||
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -61,6 +120,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.driveFilesRepository)
|
||||||
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
|
@ -70,14 +132,34 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let files: MiDriveFile[] = [];
|
||||||
|
const fileIds = ps.fileIds ?? null;
|
||||||
|
|
||||||
|
if (fileIds != null) {
|
||||||
|
files = await this.driveFilesRepository.createQueryBuilder('file')
|
||||||
|
.where('file.userId = :userId AND file.id IN (:...fileIds)', {
|
||||||
|
userId: me.id,
|
||||||
|
fileIds,
|
||||||
|
})
|
||||||
|
.orderBy('array_position(ARRAY[:...fileIds], "id"::text)')
|
||||||
|
.setParameters({ fileIds })
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
if (files.length !== fileIds.length) {
|
||||||
|
throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note.userId !== me.id) {
|
if (note.userId !== me.id) {
|
||||||
throw new ApiError(meta.errors.noSuchNote);
|
throw new ApiError(meta.errors.noSuchNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await this.notesRepository.update({ id: note.id }, {
|
await this.notesRepository.update({ id: note.id }, {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
cw: ps.cw,
|
cw: ps.cw,
|
||||||
text: ps.text,
|
text: ps.text,
|
||||||
|
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.globalEventService.publishNoteStream(note.id, 'updated', {
|
this.globalEventService.publishNoteStream(note.id, 'updated', {
|
||||||
|
|
Loading…
Reference in New Issue