Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	packages/backend/src/core/QueryService.ts
#	packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
#	packages/backend/src/server/api/endpoints/hashtags/users.ts
#	packages/backend/src/server/api/endpoints/notes/create.ts
#	packages/frontend/src/pages/emojis.emoji.vue
This commit is contained in:
mattyatea 2024-01-15 23:20:00 +09:00
commit 8205e30311
119 changed files with 12013 additions and 687 deletions

View file

@ -36,7 +36,7 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' },
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
},
required: [],
} as const;

View file

@ -60,6 +60,7 @@ export const paramDef = {
'-firstRetrievedAt',
'+latestRequestReceivedAt',
'-latestRequestReceivedAt',
null,
],
},
},

View file

@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { safeForSql } from "@/misc/safe-for-sql.js";
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
@ -47,8 +48,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, me) => {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
const query = this.usersRepository.createQueryBuilder('user')
.where(':tag IN (user.tags)', { tag: normalizeForSearch(ps.tag) })
.where(':tag <@ user.tags', { tag: [normalizeForSearch(ps.tag)] })
.andWhere('user.isSuspended = FALSE');
const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5));

View file

@ -103,13 +103,13 @@ export const meta = {
items: {
type: 'string',
enum: [
"ble",
"cable",
"hybrid",
"internal",
"nfc",
"smart-card",
"usb",
'ble',
'cable',
'hybrid',
'internal',
'nfc',
'smart-card',
'usb',
],
},
},
@ -123,8 +123,8 @@ export const meta = {
authenticatorAttachment: {
type: 'string',
enum: [
"cross-platform",
"platform",
'cross-platform',
'platform',
],
},
requireResidentKey: {
@ -133,9 +133,9 @@ export const meta = {
userVerification: {
type: 'string',
enum: [
"discouraged",
"preferred",
"required",
'discouraged',
'preferred',
'required',
],
},
},
@ -144,10 +144,11 @@ export const meta = {
type: 'string',
nullable: true,
enum: [
"direct",
"enterprise",
"indirect",
"none",
'direct',
'enterprise',
'indirect',
'none',
null,
],
},
extensions: {

View file

@ -34,11 +34,10 @@ describe('api:notes/create', () => {
.toBe(VALID);
});
// TODO
//test('null post', () => {
// expect(v({ text: null }))
// .toBe(INVALID);
//});
test('null post', () => {
expect(v({ text: null }))
.toBe(INVALID);
});
test('0 characters post', () => {
expect(v({ text: '' }))
@ -49,6 +48,11 @@ describe('api:notes/create', () => {
expect(v({ text: await tooLong }))
.toBe(INVALID);
});
test('whitespace-only post', () => {
expect(v({ text: ' ' }))
.toBe(INVALID);
});
});
describe('cw', () => {

View file

@ -213,14 +213,36 @@ export const paramDef = {
},
},
// (re)note with text, files and poll are optional
anyOf: [
{ required: ['text'] },
{ required: ['renoteId'] },
{ required: ['fileIds'] },
{ required: ['mediaIds'] },
{ required: ['poll'] },
{ required: ['schedule'] },
],
if: {
properties: {
renoteId: {
type: 'null',
},
fileIds: {
type: 'null',
},
mediaIds: {
type: 'null',
},
poll: {
type: 'null',
},
schedule:{
type: 'null'
}
},
},
then: {
properties: {
text: {
type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
pattern: '[^\\s]+',
},
},
required: ['text'],
},
} as const;
@Injectable()

View file

@ -61,9 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => {
qb
.where(`'{"${me.id}"}' <@ note.mentions`)
.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
qb // このmeIdAsListパラメータはqueryServiceのgenerateVisibilityQueryでセットされる
.where(':meIdAsList <@ note.mentions')
.orWhere(':meIdAsList <@ note.visibleUserIds');
}))
// Avoid scanning primary key index
.orderBy('CONCAT(note.id)', 'DESC')

View file

@ -87,14 +87,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
try {
if (ps.tag) {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
} else {
query.andWhere(new Brackets(qb => {
for (const tags of ps.query!) {
qb.orWhere(new Brackets(qb => {
for (const tag of tags) {
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
qb.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(tag)] });
}
}));
}