update backend to the new templates

This commit is contained in:
Hazelnoot 2025-10-07 00:40:02 -04:00
parent 4e30986cda
commit 05be2596ea
44 changed files with 156 additions and 111 deletions

View file

@ -767,7 +767,7 @@ export class DriveService {
@bindThis
public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: { id: string }) {
const promises = [];
const promises: Promise<void>[] = [];
if (file.storedInternal) {
promises.push(this.deleteLocalFile(file.accessKey!));

View file

@ -299,7 +299,7 @@ export class MfmService {
(note that the `rp` are to be ignored, they only exist
for browsers who don't understand ruby)
*/
let nonRtNodes = [];
let nonRtNodes: ChildNode[] = [];
// scan children, ignore `rp`, split on `rt`
for (const child of node.childNodes) {
if (isText(child)) {

View file

@ -147,7 +147,7 @@ export class ReactionsBufferingService implements OnApplicationShutdown {
// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
@bindThis
public async bake(): Promise<void> {
const bufferedNoteIds = [];
const bufferedNoteIds: string[] = [];
let cursor = '0';
do {
// https://github.com/redis/ioredis#transparent-key-prefixing

View file

@ -40,7 +40,7 @@ export class FlashEntityService {
// { schema: 'UserDetailed' } すると無限ループするので注意
const user = hint?.packedUser ?? await this.userEntityService.pack(flash.user ?? flash.userId, me);
let isLiked = undefined;
let isLiked: boolean | undefined = undefined;
if (meId) {
isLiked = hint?.likedFlashIds
? hint.likedFlashIds.includes(flash.id)

View file

@ -51,8 +51,8 @@ export class MetaEntityService {
.getMany();
// クライアントの手間を減らすためあらかじめJSONに変換しておく
let defaultLightTheme = null;
let defaultDarkTheme = null;
let defaultLightTheme: string | null = null;
let defaultDarkTheme: string | null = null;
if (instance.defaultLightTheme) {
try {
defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme));

View file

@ -491,7 +491,7 @@ export class NoteEntityService implements OnModuleInit {
@bindThis
public async packAttachedFiles(fileIds: MiNote['fileIds'], packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>): Promise<Packed<'DriveFile'>[]> {
const missingIds = [];
const missingIds: string[] = [];
for (const id of fileIds) {
if (!packedFiles.has(id)) missingIds.push(id);
}

View file

@ -232,7 +232,7 @@ export class NotificationEntityService implements OnModuleInit {
validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId));
const userIds = [];
const userIds: string[] = [];
for (const notification of validNotifications) {
if ('notifierId' in notification) userIds.push(notification.notifierId);
if (notification.type === 'reaction:grouped') userIds.push(...notification.reactions.map(x => x.userId));

View file

@ -4,7 +4,7 @@
*/
function parseBigIntChunked(str: string, base: number, chunkSize: number, powerOfChunkSize: bigint): bigint {
const chunks = [];
const chunks: string[] = [];
while (str.length > 0) {
chunks.unshift(str.slice(-chunkSize));
str = str.slice(0, -chunkSize);

View file

@ -12,7 +12,7 @@ export function generateInviteCode(now: number): string {
chars: CHARS,
});
const uniqueId = [];
const uniqueId: string[] = [];
let n = Math.floor(now / 1000 / 60);
while (true) {
uniqueId.push(CHARS[n % CHARS.length]);

View file

@ -80,7 +80,7 @@ export function lessThan(xs: number[], ys: number[]): boolean {
* Returns the longest prefix of elements that satisfy the predicate
*/
export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] {
const ys = [];
const ys: T[] = [];
for (const x of xs) {
if (f(x)) {
ys.push(x);

View file

@ -9,7 +9,7 @@ import type { HttpRequestService } from '@/core/HttpRequestService.js';
type Field = { name: string, value: string };
export async function verifyFieldLinks(fields: Field[], profileUrls: string[], httpRequestService: HttpRequestService): Promise<string[]> {
const verified_links = [];
const verified_links: string[] = [];
for (const field_url of fields) {
try {
// getHtml validates the input URL, so we can safely pass in untrusted values

View file

@ -149,7 +149,7 @@ class TypeORMLogger implements Logger {
@bindThis
private transformParameters(parameters?: unknown[]): Data | undefined {
if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) {
return parameters.reduce((params: Record<string, string>, p, i) => {
return parameters.reduce<Record<string, string>>((params: Record<string, string>, p, i) => {
params[`$${i + 1}`] = stringifyParameter(p);
return params;
}, {} as Record<string, string>);

View file

@ -397,7 +397,7 @@ export class ImportNotesProcessorService {
const visibility = followers ? toot.cc.includes('https://www.w3.org/ns/activitystreams#Public') ? 'home' : 'followers' : 'public';
const date = new Date(toot.object.published);
let text = undefined;
let text: string | undefined = undefined;
const files: MiDriveFile[] = [];
let reply: MiNote | null = null;
@ -464,7 +464,7 @@ export class ImportNotesProcessorService {
}
const date = new Date(post.object.published);
let text = undefined;
let text: string | undefined = undefined;
const files: MiDriveFile[] = [];
let reply: MiNote | null = null;
@ -547,7 +547,7 @@ export class ImportNotesProcessorService {
const files: MiDriveFile[] = [];
function decodeIGString(str: string) {
const arr = [];
const arr: number[] = [];
for (let i = 0; i < str.length; i++) {
arr.push(str.charCodeAt(i));
}
@ -700,7 +700,7 @@ export class ImportNotesProcessorService {
const files: MiDriveFile[] = [];
function decodeFBString(str: string) {
const arr = [];
const arr: number[] = [];
for (let i = 0; i < str.length; i++) {
arr.push(str.charCodeAt(i));
}
@ -708,7 +708,7 @@ export class ImportNotesProcessorService {
}
if (post.attachments && this.isIterable(post.attachments)) {
const media = [];
const media: any[] = [];
for await (const data of post.attachments[0].data) {
if (data.media) {
media.push(data.media);

View file

@ -63,7 +63,11 @@ export const meta = {
},
} as const;
export const paramDef = {} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export

View file

@ -14,8 +14,6 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:cw-instance',
res: {},
} as const;
export const paramDef = {

View file

@ -19,8 +19,6 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:cw-note',
res: {},
} as const;
export const paramDef = {

View file

@ -17,8 +17,6 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:cw-user',
res: {},
} as const;
export const paramDef = {
@ -63,6 +61,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userUsername: user.username,
userHost: user.host,
});
return {};
});
}
}

View file

@ -167,6 +167,7 @@ export const paramDef = {
fileId: { type: 'string', format: 'misskey:id' },
url: { type: 'string' },
},
// TODO it chokes on this
anyOf: [
{ required: ['fileId'] },
{ required: ['url'] },

View file

@ -102,8 +102,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
case 'NO_SUCH_EMOJI': throw new ApiError(meta.errors.noSuchEmoji);
case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists);
}
// 網羅性チェック
const mustBeNever: never = error;
});
}
}

View file

@ -17,7 +17,11 @@ export const meta = {
kind: 'write:admin:meta',
} as const;
export const paramDef = {} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export

View file

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RegistrationTicketsRepository } from '@/models/_.js';
import type { RegistrationTicketsRepository, MiRegistrationTicket } from '@/models/_.js';
import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js';
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.invalidDateTime);
}
const ticketsPromises = [];
const ticketsPromises: Promise<MiRegistrationTicket>[] = [];
for (let i = 0; i < ps.count; i++) {
ticketsPromises.push(this.registrationTicketsRepository.insertOne({

View file

@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { ChannelsRepository, DriveFilesRepository } from '@/models/_.js';
import type { ChannelsRepository, DriveFilesRepository, MiDriveFile } from '@/models/_.js';
import type { MiChannel } from '@/models/Channel.js';
import { IdService } from '@/core/IdService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private channelEntityService: ChannelEntityService,
) {
super(meta, paramDef, async (ps, me) => {
let banner = null;
let banner: MiDriveFile | null = null;
if (ps.bannerId != null) {
banner = await this.driveFilesRepository.findOneBy({
id: ps.bannerId,

View file

@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository, ChannelsRepository } from '@/models/_.js';
import type { DriveFilesRepository, ChannelsRepository, MiDriveFile } from '@/models/_.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
@ -99,8 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.accessDenied);
}
// eslint:disable-next-line:no-unnecessary-initializer
let banner = undefined;
let banner: MiDriveFile | null = null;
if (ps.bannerId != null) {
banner = await this.driveFilesRepository.findOneBy({
id: ps.bannerId,

View file

@ -10,7 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { ChatService } from '@/core/ChatService.js';
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
import type { DriveFilesRepository, MiUser, MiDriveFile } from '@/models/_.js';
import type { Config } from '@/config.js';
export const meta = {
@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRoom);
}
let file = null;
let file: MiDriveFile | null = null;
if (ps.fileId != null) {
file = await this.driveFilesRepository.findOneBy({
id: ps.fileId,

View file

@ -10,7 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { ChatService } from '@/core/ChatService.js';
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
import type { DriveFilesRepository, MiUser, MiDriveFile } from '@/models/_.js';
import type { Config } from '@/config.js';
export const meta = {
@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.maxLength);
}
let file = null;
let file: MiDriveFile | null = null;
if (ps.fileId != null) {
file = await this.driveFilesRepository.findOneBy({
id: ps.fileId,

View file

@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFoldersRepository } from '@/models/_.js';
import type { DriveFoldersRepository, MiDriveFolder } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
// If the parent folder is specified
let parent = null;
let parent: MiDriveFolder | null = null;
if (ps.parentId) {
// Fetch parent folder
parent = await this.driveFoldersRepository.findOneBy({

View file

@ -20,8 +20,6 @@ export const meta = {
},
},
res: {},
// 10 calls per 5 seconds
limit: {
duration: 1000 * 5,

View file

@ -5,7 +5,7 @@
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
import type { DriveFilesRepository, PagesRepository, MiDriveFile } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { MiPage, pageNameSchema } from '@/models/Page.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private readonly timeService: TimeService,
) {
super(meta, paramDef, async (ps, me) => {
let eyeCatchingImage = null;
let eyeCatchingImage: MiDriveFile | null = null;
if (ps.eyeCatchingImageId != null) {
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
id: ps.eyeCatchingImageId,

View file

@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import { getErrorData, getErrorException, getErrorStatus, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
@ -118,7 +120,10 @@ export class MastodonApiServerService {
}
const client = this.clientService.getClient(_request);
const data = await client.uploadMedia(multipartData);
const data = await client.uploadMedia({
...multipartData,
stream: Readable.toWeb(createReadStream(multipartData.filepath)),
});
const response = convertAttachment(data.data as Entity.Attachment);
return reply.send(response);
@ -131,7 +136,10 @@ export class MastodonApiServerService {
}
const client = this.clientService.getClient(_request);
const data = await client.uploadMedia(multipartData, _request.body);
const data = await client.uploadMedia({
...multipartData,
stream: Readable.toWeb(createReadStream(multipartData.filepath)),
}, _request.body);
const response = convertAttachment(data.data as Entity.Attachment);
return reply.send(response);

View file

@ -6,8 +6,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Entity, MastodonEntity, MisskeyEntity } from 'megalodon';
import * as mfm from 'mfm-js';
import { MastodonNotificationType } from 'megalodon/lib/src/mastodon/notification.js';
import { NotificationType } from 'megalodon/lib/src/notification.js';
import { MastodonNotificationType } from 'megalodon/built/lib/mastodon/notification.js';
import { NotificationType } from 'megalodon/built/lib/notification.js';
import { DI } from '@/di-symbols.js';
import { MfmService } from '@/core/MfmService.js';
import type { Config } from '@/config.js';

View file

@ -8,8 +8,8 @@
window.onload = async () => {
const content = document.getElementById('content');
document.getElementById('ls').addEventListener('click', () => {
content.innerHTML = '';
document.getElementById('ls')?.addEventListener('click', () => {
if (content) content.innerHTML = '';
const lsEditor = document.createElement('div');
lsEditor.id = 'lsEditor';
@ -31,7 +31,7 @@ window.onload = async () => {
lsEditor.appendChild(adder);
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
const k = /** @type {string} */ (localStorage.key(i));
const record = document.createElement('div');
record.classList.add('record');
const header = document.createElement('header');
@ -57,6 +57,6 @@ window.onload = async () => {
lsEditor.appendChild(record);
}
content.appendChild(lsEditor);
content?.appendChild(lsEditor);
});
};

View file

@ -43,6 +43,7 @@
//#region Detect language & fetch translations
if (!localStorage.getItem('locale')) {
const supportedLangs = LANGS;
/** @type {string | null | undefined} */
let lang = localStorage.getItem('lang');
if (lang == null || !supportedLangs.includes(lang)) {
if (supportedLangs.includes(navigator.language)) {
@ -92,12 +93,20 @@
}
//#endregion
/**
* @param {string} styleText
* @returns {Promise<void>}
*/
async function addStyle(styleText) {
let css = document.createElement('style');
css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css);
}
/**
* @param {string} code
* @returns {Promise<void>}
*/
async function renderError(code) {
// Cannot set property 'innerHTML' of null を回避
if (document.readyState === 'loading') {

View file

@ -34,6 +34,7 @@
//#region Detect language & fetch translations
if (!localStorage.getItem('locale')) {
const supportedLangs = LANGS;
/** @type {string | null | undefined} */
let lang = localStorage.getItem('lang');
if (lang == null || !supportedLangs.includes(lang)) {
if (supportedLangs.includes(navigator.language)) {
@ -154,12 +155,21 @@
document.head.appendChild(style);
}
/**
* @param {string} styleText
* @returns {Promise<void>}
*/
async function addStyle(styleText) {
let css = document.createElement('style');
css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css);
}
/**
* @param {string} code
* @param {any} [details]
* @returns {Promise<void>}
*/
async function renderError(code, details) {
// Cannot set property 'innerHTML' of null を回避
if (document.readyState === 'loading') {
@ -233,7 +243,7 @@
<code>ERROR CODE: ${code}</code>
</summary>
<code>${details.toString()} ${JSON.stringify(details)}</code>`;
errorsElement.appendChild(detailsElement);
errorsElement?.appendChild(detailsElement);
addStyle(`
* {
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;

View file

@ -6,9 +6,15 @@
'use strict';
window.onload = async () => {
const account = JSON.parse(localStorage.getItem('account'));
const i = account.token;
const accountRaw = localStorage.getItem('account');
const account = accountRaw ? JSON.parse(accountRaw) : null;
const i = account?.token;
/**
* @param {string} endpoint
* @param {Record<string, unknown>} data
* @returns {Promise<any>}
*/
const api = (endpoint, data = {}) => {
const promise = new Promise((resolve, reject) => {
// Append a credential
@ -17,19 +23,19 @@ window.onload = async () => {
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
cache: 'no-cache',
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
resolve({});
} else {
reject(body.error);
}
@ -39,7 +45,7 @@ window.onload = async () => {
return promise;
};
document.getElementById('submit').addEventListener('click', () => {
document.getElementById('submit')?.addEventListener('click', () => {
api('notes/create', {
text: (/** @type {HTMLInputElement} */(document.getElementById('text'))).value
}).then(() => {
@ -49,6 +55,7 @@ window.onload = async () => {
api('notes/timeline').then(notes => {
const tl = document.getElementById('tl');
if (!tl) return;
for (const note of notes) {
const el = document.createElement('div');
const name = document.createElement('header');