Compare commits

..

8 commits

Author SHA1 Message Date
Johann150 af80492c16
fixup: use structuredClone 2022-12-13 22:07:36 +01:00
Johann150 94182876c6
server: add v2 routes to notes endpoints 2022-12-13 21:02:25 +01:00
Johann150 0d15b74193
improve type definitions for v2 method
The method has to be lowercase because it is used as an index to get
the respective method of the router.
2022-12-13 21:01:26 +01:00
Andy 7fd6ea563f
api-doc: don't override route docs with each new HTTP method 2022-12-13 21:01:26 +01:00
Andy 91b97e4980
improve fetching of endpoint arguments
including support for route parameters (e.g. '/v2/note/:noteId' giving us a 'noteId' value)

Co-authored-by: Johann150 <johann.galle@protonmail.com>
2022-12-13 21:01:14 +01:00
Andy b5b5dd51af
generate OpenAPI spec for v2 endpoints 2022-12-13 20:59:34 +01:00
Andy 5c0cf99b59
WIP: make v2 meta endpoint support GET 2022-12-13 20:59:32 +01:00
Andy 096f2129ab
WIP: Add additional handling of endpoints with v2 options 2022-12-13 20:57:24 +01:00
125 changed files with 716 additions and 382 deletions

View file

@ -1,4 +1,5 @@
.autogen
.vscode
.config
.woodpecker
Dockerfile

View file

@ -2,10 +2,9 @@ root = true
[*]
indent_style = tab
indent_size = 4
indent_size = 2
charset = utf-8
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
# Visual Studio Code
/.vscode
/.vsls.json
!/.vscode/extensions.json
# Intelij-IDEA
/.idea

8
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin"
]
}

4
.vsls.json Normal file
View file

@ -0,0 +1,4 @@
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore": "exclude"
}

View file

@ -1,22 +0,0 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1 # CI does not need commit history
recursive: true
pipeline:
install:
when:
event:
- pull_request
image: node:18.6.0
commands:
- yarn install
lint:
when:
event:
- pull_request
image: node:18.6.0
commands:
- yarn workspace sw run lint

View file

@ -36,7 +36,7 @@ gulp.task('copy:client:locales', cb => {
});
gulp.task('build:backend:script', () => {
return gulp.src(['./packages/backend/src/server/web/boot.js'])
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(terser({
toplevel: true
@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
});
gulp.task('build:backend:style', () => {
return gulp.src(['./packages/backend/src/server/web/style.css'])
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
.pipe(cssnano({
zindex: false
}))

View file

@ -96,8 +96,6 @@ unfollow: "Unfollow"
followRequestPending: "Follow request pending"
renote: "Renote"
unrenote: "Take back renote"
unrenoteAll: "Take back all renotes"
unrenoteAllConfirm: "Are you sure that you want to take back all renotes of this note?"
quote: "Quote"
pinnedNote: "Pinned note"
you: "You"

View file

@ -46,11 +46,11 @@
"devDependencies": {
"@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/parser": "^5.46.1",
"@typescript-eslint/parser": "^5.44.0",
"cross-env": "7.0.3",
"cypress": "10.3.0",
"start-server-and-test": "1.14.0",
"typescript": "^4.9.4"
"typescript": "^4.9.3"
},
"packageManager": "yarn@3.3.0"
}

View file

@ -7,7 +7,7 @@
"scripts": {
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"lint": "tsc --noEmit && eslint src --ext .ts",
"lint": "eslint src --ext .ts",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"migrate": "npx typeorm migration:run -d ormconfig.js",
"start": "node --experimental-json-modules ./built/index.js",
@ -166,14 +166,14 @@
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"cross-env": "7.0.3",
"eslint": "^8.29.0",
"eslint-plugin-import": "^2.26.0",
"execa": "6.1.0",
"form-data": "^4.0.0",
"sinon": "^14.0.2",
"typescript": "^4.9.4"
"typescript": "^4.9.3"
}
}

View file

@ -40,7 +40,7 @@ export default function load(): Config {
config.images = Object.assign({
info: '/twemoji/1f440.svg',
notFound: '/twemoji/2049.svg',
notFound: '/twemoji/2040.svg',
error: '/twemoji/1f480.svg',
}, config.images ?? {});

View file

@ -1,5 +1,6 @@
// https://github.com/typeorm/typeorm/issues/2400
import pg from 'pg';
import { SECOND } from '@/const.js';
pg.types.setTypeParser(20, Number);
@ -7,7 +8,6 @@ import { Logger, DataSource } from 'typeorm';
import * as highlight from 'cli-highlight';
import config from '@/config/index.js';
import { SECOND } from '@/const.js';
import { User } from '@/models/entities/user.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { DriveFolder } from '@/models/entities/drive-folder.js';
@ -78,33 +78,33 @@ import { redisClient } from './redis.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
class MyCustomLogger implements Logger {
private highlight(sql: string): string {
private highlight(sql: string) {
return highlight.highlight(sql, {
language: 'sql', ignoreIllegals: true,
});
}
public logQuery(query: string): void {
public logQuery(query: string, parameters?: any[]) {
sqlLogger.info(this.highlight(query).substring(0, 100));
}
public logQueryError(error: string, query: string): void {
public logQueryError(error: string, query: string, parameters?: any[]) {
sqlLogger.error(this.highlight(query));
}
public logQuerySlow(time: number, query: string): void {
public logQuerySlow(time: number, query: string, parameters?: any[]) {
sqlLogger.warn(this.highlight(query));
}
public logSchemaBuild(message: string): void {
public logSchemaBuild(message: string) {
sqlLogger.info(message);
}
public log(message: string): void {
public log(message: string) {
sqlLogger.info(message);
}
public logMigration(message: string): void {
public logMigration(message: string) {
sqlLogger.info(message);
}
}

View file

@ -1,7 +1,7 @@
import * as crypto from 'node:crypto';
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
const LU_CHARS = L_CHARS + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
export function secureRndstrCustom(length = 32, chars: string): string {
const chars_len = chars.length;

View file

@ -17,7 +17,7 @@ export class AbuseUserReport {
@Column(id())
public targetUserId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class AbuseUserReport {
@Column(id())
public reporterId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -39,7 +39,7 @@ export class AbuseUserReport {
})
public assigneeId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -41,7 +41,7 @@ export class AccessToken {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -53,7 +53,7 @@ export class AccessToken {
})
public appId: App['id'] | null;
@ManyToOne(() => App, {
@ManyToOne(type => App, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class AnnouncementRead {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -28,7 +28,7 @@ export class AnnouncementRead {
@Column(id())
public announcementId: Announcement['id'];
@ManyToOne(() => Announcement, {
@ManyToOne(type => Announcement, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class AntennaNote {
})
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class AntennaNote {
})
public antennaId: Antenna['id'];
@ManyToOne(() => Antenna, {
@ManyToOne(type => Antenna, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Antenna {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -42,7 +42,7 @@ export class Antenna {
})
public userListId: UserList['id'] | null;
@ManyToOne(() => UserList, {
@ManyToOne(type => UserList, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -54,7 +54,7 @@ export class Antenna {
})
public userGroupJoiningId: UserGroupJoining['id'] | null;
@ManyToOne(() => UserGroupJoining, {
@ManyToOne(type => UserGroupJoining, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class App {
})
public userId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'SET NULL',
nullable: true,
})

View file

@ -11,7 +11,7 @@ export class AttestationChallenge {
@PrimaryColumn(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
import { Entity, PrimaryColumn, Index, Column, ManyToOne, OneToOne, JoinColumn } from 'typeorm';
import { id } from '../id.js';
import { AccessToken } from './access-token.js';
import { App } from './app.js';

View file

@ -21,7 +21,7 @@ export class Blocking {
})
public blockeeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class Blocking {
})
public blockerId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class ChannelFollowing {
})
public followeeId: Channel['id'];
@ManyToOne(() => Channel, {
@ManyToOne(type => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class ChannelFollowing {
})
public followerId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class ChannelNotePining {
@Column(id())
public channelId: Channel['id'];
@ManyToOne(() => Channel, {
@ManyToOne(type => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class ChannelNotePining {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -28,7 +28,7 @@ export class Channel {
})
public userId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'SET NULL',
})
@JoinColumn()
@ -53,7 +53,7 @@ export class Channel {
})
public bannerId: DriveFile['id'] | null;
@ManyToOne(() => DriveFile, {
@ManyToOne(type => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class ClipNote {
})
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class ClipNote {
})
public clipId: Clip['id'];
@ManyToOne(() => Clip, {
@ManyToOne(type => Clip, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class Clip {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -23,7 +23,7 @@ export class DriveFile {
})
public userId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'RESTRICT',
})
@JoinColumn()
@ -144,7 +144,7 @@ export class DriveFile {
})
public folderId: DriveFolder['id'] | null;
@ManyToOne(() => DriveFolder, {
@ManyToOne(type => DriveFolder, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class DriveFolder {
})
public userId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -41,7 +41,7 @@ export class DriveFolder {
})
public parentId: DriveFolder['id'] | null;
@ManyToOne(() => DriveFolder, {
@ManyToOne(type => DriveFolder, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -20,7 +20,7 @@ export class FollowRequest {
})
public followeeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -33,7 +33,7 @@ export class FollowRequest {
})
public followerId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Following {
})
public followeeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class Following {
})
public followerId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class GalleryLike {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class GalleryLike {
@Column(id())
public postId: GalleryPost['id'];
@ManyToOne(() => GalleryPost, {
@ManyToOne(type => GalleryPost, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -37,7 +37,7 @@ export class GalleryPost {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class MessagingMessage {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class MessagingMessage {
})
public recipientId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -48,7 +48,7 @@ export class MessagingMessage {
})
public groupId: UserGroup['id'] | null;
@ManyToOne(() => UserGroup, {
@ManyToOne(type => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -81,7 +81,7 @@ export class MessagingMessage {
})
public fileId: DriveFile['id'] | null;
@ManyToOne(() => DriveFile, {
@ManyToOne(type => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -134,7 +134,7 @@ export class Meta {
})
public proxyAccountId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class ModerationLog {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -17,7 +17,7 @@ export class MutedNote {
})
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -30,7 +30,7 @@ export class MutedNote {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Muting {
})
public muteeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -40,7 +40,7 @@ export class Muting {
})
public muterId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class NoteFavorite {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class NoteFavorite {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class NoteReaction {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class NoteReaction {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -20,7 +20,7 @@ export class NoteThreadMuting {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -14,7 +14,7 @@ export class NoteUnread {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -24,7 +24,7 @@ export class NoteUnread {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class NoteWatching {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class NoteWatching {
})
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Note {
})
public replyId: Note['id'] | null;
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -41,7 +41,7 @@ export class Note {
})
public renoteId: Note['id'] | null;
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -75,7 +75,7 @@ export class Note {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -179,7 +179,7 @@ export class Note {
})
public channelId: Channel['id'] | null;
@ManyToOne(() => Channel, {
@ManyToOne(type => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -28,7 +28,7 @@ export class Notification {
})
public notifieeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -45,7 +45,7 @@ export class Notification {
})
public notifierId: User['id'] | null;
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -89,7 +89,7 @@ export class Notification {
})
public noteId: Note['id'] | null;
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -101,7 +101,7 @@ export class Notification {
})
public followRequestId: FollowRequest['id'] | null;
@ManyToOne(() => FollowRequest, {
@ManyToOne(type => FollowRequest, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -113,7 +113,7 @@ export class Notification {
})
public userGroupInvitationId: UserGroupInvitation['id'] | null;
@ManyToOne(() => UserGroupInvitation, {
@ManyToOne(type => UserGroupInvitation, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -165,7 +165,7 @@ export class Notification {
})
public appAccessTokenId: AccessToken['id'] | null;
@ManyToOne(() => AccessToken, {
@ManyToOne(type => AccessToken, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class PageLike {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class PageLike {
@Column(id())
public pageId: Page['id'];
@ManyToOne(() => Page, {
@ManyToOne(type => Page, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -57,7 +57,7 @@ export class Page {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -69,7 +69,7 @@ export class Page {
})
public eyeCatchingImageId: DriveFile['id'] | null;
@ManyToOne(() => DriveFile, {
@ManyToOne(type => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class PasswordResetRequest {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class PollVote {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class PollVote {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -9,7 +9,7 @@ export class Poll {
@PrimaryColumn(id())
public noteId: Note['id'];
@OneToOne(() => Note, {
@OneToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -25,7 +25,7 @@ export class RegistryItem {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class RenoteMuting {
})
public muteeId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class RenoteMuting {
})
public muterId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class Signin {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -14,7 +14,7 @@ export class SwSubscription {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserGroupInvitation {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserGroupInvitation {
})
public userGroupId: UserGroup['id'];
@ManyToOne(() => UserGroup, {
@ManyToOne(type => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserGroupJoining {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserGroupJoining {
})
public userGroupId: UserGroup['id'];
@ManyToOne(() => UserGroup, {
@ManyToOne(type => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -25,7 +25,7 @@ export class UserGroup {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -7,7 +7,7 @@ export class UserKeypair {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(() => User, {
@OneToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserListJoining {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserListJoining {
})
public userListId: UserList['id'];
@ManyToOne(() => UserList, {
@ManyToOne(type => UserList, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class UserList {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class UserNotePining {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class UserNotePining {
@Column(id())
public noteId: Note['id'];
@ManyToOne(() => Note, {
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -11,7 +11,7 @@ export class UserProfile {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(() => User, {
@OneToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -161,7 +161,7 @@ export class UserProfile {
})
public pinnedPageId: Page['id'] | null;
@OneToOne(() => Page, {
@OneToOne(type => Page, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -7,7 +7,7 @@ export class UserPublickey {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(() => User, {
@OneToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -13,7 +13,7 @@ export class UserSecurityKey {
@Column(id())
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -81,7 +81,7 @@ export class User {
})
public avatarId: DriveFile['id'] | null;
@OneToOne(() => DriveFile, {
@OneToOne(type => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()
@ -94,7 +94,7 @@ export class User {
})
public bannerId: DriveFile['id'] | null;
@OneToOne(() => DriveFile, {
@OneToOne(type => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Webhook {
})
public userId: User['id'];
@ManyToOne(() => User, {
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
const properties = structuredClone(file.properties);
// TODO
//const properties = structuredClone(file.properties);
const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) {
[properties.width, properties.height] = [properties.height, properties.width];
}

View file

@ -20,7 +20,7 @@ export function initialize<T>(name: string, limitPerSec = -1): Bull.Queue<T> {
}
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
function apBackoff(attemptsMade: number /*, err: Error */): number {
function apBackoff(attemptsMade: number, err: Error) {
const baseDelay = MINUTE;
const maxBackoff = 8 * HOUR;
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;

View file

@ -1,7 +1,7 @@
import escapeRegexp from 'escape-regexp';
import config from '@/config/index.js';
import { Note } from '@/models/entities/note.js';
import { CacheableUser } from '@/models/entities/user.js';
import { CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Notes, MessagingMessages } from '@/models/index.js';
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';

View file

@ -1,3 +1,4 @@
import { IsNull, Not } from 'typeorm';
import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js';
import { Users, Followings } from '@/models/index.js';
import { deliver } from '@/queue/index.js';

View file

@ -9,7 +9,7 @@ import { getApId, IObject, ICreate } from '@/remote/activitypub/type.js';
/**
* 稿
*/
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false): Promise<string> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
const uri = getApId(note);
if (typeof note === 'object') {

View file

@ -1,5 +1,5 @@
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js';
import { getApType, IUpdate, isActor } from '@/remote/activitypub/type.js';
import { apLogger } from '@/remote/activitypub/logger.js';
import { updateQuestion } from '@/remote/activitypub/models/question.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
@ -21,11 +21,7 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: R
});
if (isActor(object)) {
if (actor.uri !== getApId(object)) {
return 'skip: actor id !== updated actor id';
}
await updatePerson(object, resolver);
await updatePerson(actor.uri!, resolver, object);
return 'ok: Person updated';
} else if (getApType(object) === 'Question') {
await updateQuestion(object, resolver).catch(e => console.log(e));

View file

@ -5,7 +5,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js';
import { IObject, isMention, IApMention } from '../type.js';
import { resolvePerson } from './person.js';
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise<CacheableUser[]> {
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
const limit = promiseLimit<CacheableUser | null>(2);

View file

@ -28,7 +28,9 @@ import { extractApHashtags } from './tag.js';
import { extractPollFromQuestion } from './question.js';
import { extractApMentions } from './mention.js';
export function validateNote(object: IObject): Error | null {
export function validateNote(object: any, uri: string): Error | null {
const expectHost = extractDbHost(uri);
if (object == null) {
return new Error('invalid Note: object is null');
}
@ -37,20 +39,12 @@ export function validateNote(object: IObject): Error | null {
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
}
const id = getApId(object);
if (id == null) {
// Only transient objects or anonymous objects may not have an id or an id that is explicitly null.
// We consider all Notes as not transient and not anonymous so require ids for them.
return new Error(`invalid Note: id required but not present`);
if (object.id && extractDbHost(object.id) !== expectHost) {
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`);
}
// Check that the server is authorized to act on behalf of this author.
const expectHost = extractDbHost(id);
const attributedToHost = object.attributedTo
? extractDbHost(getOneApId(object.attributedTo))
: null;
if (attributedToHost !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`);
if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`);
}
return null;
@ -70,9 +64,10 @@ export async function fetchNote(object: string | IObject): Promise<Note | null>
* Noteを作成します
*/
export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise<Note | null> {
const object: IObject = await resolver.resolve(value);
const object: any = await resolver.resolve(value);
const err = validateNote(object);
const entryUri = getApId(value);
const err = validateNote(object, entryUri);
if (err) {
apLogger.error(`${err.message}`, {
resolver: {

View file

@ -13,7 +13,7 @@ import { genId } from '@/misc/gen-id.js';
import { instanceChart, usersChart } from '@/services/chart/index.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { extractDbHost } from '@/misc/convert-host.js';
import { toPuny } from '@/misc/convert-host.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { toArray } from '@/prelude/array.js';
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
@ -39,7 +39,8 @@ const summaryLength = 2048;
* @param x Fetched object
* @param uri Fetch target URI
*/
function validateActor(x: IObject): IActor {
function validateActor(x: IObject, uri: string): IActor {
const expectHost = toPuny(new URL(uri).hostname);
if (x == null) {
throw new Error('invalid Actor: object is null');
@ -49,10 +50,7 @@ function validateActor(x: IObject): IActor {
throw new Error(`invalid Actor type '${x.type}'`);
}
const uri = getApId(x);
if (uri == null) {
// Only transient objects or anonymous objects may not have an id or an id that is explicitly null.
// We consider all actors as not transient and not anonymous so require ids for them.
if (!(typeof x.id === 'string' && x.id.length > 0)) {
throw new Error('invalid Actor: wrong id');
}
@ -80,13 +78,17 @@ function validateActor(x: IObject): IActor {
x.summary = truncate(x.summary, summaryLength);
}
const idHost = toPuny(new URL(x.id!).hostname);
if (idHost !== expectHost) {
throw new Error('invalid Actor: id has different host');
}
if (x.publicKey) {
if (typeof x.publicKey.id !== 'string') {
throw new Error('invalid Actor: publicKey.id is not a string');
}
const expectHost = extractDbHost(uri);
const publicKeyIdHost = extractDbHost(x.publicKey.id);
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname);
if (publicKeyIdHost !== expectHost) {
throw new Error('invalid Actor: publicKey.id has different host');
}
@ -129,18 +131,20 @@ export async function fetchPerson(uri: string, resolver: Resolver): Promise<Cach
/**
* Personを作成します
*/
export async function createPerson(value: string | IObject, resolver: Resolver): Promise<User> {
if (getApId(value).startsWith(config.url)) {
export async function createPerson(uri: string, resolver: Resolver): Promise<User> {
if (typeof uri !== 'string') throw new Error('uri is not string');
if (uri.startsWith(config.url)) {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
}
const object = await resolver.resolve(value) as any;
const object = await resolver.resolve(uri) as any;
const person = validateActor(object);
const person = validateActor(object, uri);
apLogger.info(`Creating the Person: ${person.id}`);
const host = extractDbHost(object.id);
const host = toPuny(new URL(object.id).hostname);
const { fields } = analyzeAttachments(person.attachment || []);
@ -270,32 +274,33 @@ export async function createPerson(value: string | IObject, resolver: Resolver):
/**
* Update Person information.
* If the target Person is not registered in FoundKey, it is ignored.
* @param value URI of Person or Person itself
* @param uri URI of Person
* @param resolver Resolver
* @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve.)
*/
export async function updatePerson(value: IObject | string, resolver: Resolver): Promise<void> {
const uri = getApId(value);
export async function updatePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<void> {
if (typeof uri !== 'string') throw new Error('uri is not string');
// skip local URIs
if (uri.startsWith(config.url)) {
// URIがこのサーバーを指しているならスキップ
if (uri.startsWith(config.url + '/')) {
return;
}
// do we already know this user?
//#region このサーバーに既に登録されているか
const exist = await Users.findOneBy({ uri }) as IRemoteUser;
if (exist == null) {
return;
}
//#endregion
const object = await resolver.resolve(value);
const object = hint || await resolver.resolve(uri);
const person = validateActor(object);
const person = validateActor(object, uri);
apLogger.info(`Updating the Person: ${person.id}`);
// Fetch avatar and banner image
// アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([
person.icon,
person.image,
@ -305,7 +310,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
: resolveImage(exist, img, resolver).catch(() => null),
));
// Get custom emoji
// カスタム絵文字取得
const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => {
apLogger.info(`extractEmojis: ${e}`);
return [] as Emoji[];
@ -313,7 +318,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
const emojiNames = emojis.map(emoji => emoji.name);
const { fields } = analyzeAttachments(person.attachment ?? []);
const { fields } = analyzeAttachments(person.attachment || []);
const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32);
@ -322,7 +327,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
const updates = {
lastFetchedAt: new Date(),
inbox: person.inbox,
sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
followersUri: person.followers ? getApId(person.followers) : undefined,
featured: person.featured,
emojis: emojiNames,
@ -362,13 +367,14 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
publishInternalEvent('remoteUserUpdated', { id: exist.id });
// ハッシュタグ更新
updateUsertags(exist, tags);
// If the user in question is already a follower, followers will also be updated.
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
await Followings.update({
followerId: exist.id,
}, {
followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
});
await updateFeatured(exist.id, resolver).catch(err => apLogger.error(err));

View file

@ -2,20 +2,13 @@ import config from '@/config/index.js';
import { Relay } from '@/models/entities/relay.js';
import { ILocalUser } from '@/models/entities/user.js';
export type FollowRelay = {
id: string;
type: 'Follow';
actor: string;
object: 'https://www.w3.org/ns/activitystreams#Public';
};
export function renderFollowRelay(relay: Relay, relayActor: ILocalUser): FollowRelay {
export function renderFollowRelay(relay: Relay, relayActor: ILocalUser) {
const follow = {
id: `${config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow',
actor: `${config.url}/users/${relayActor.id}`,
object: 'https://www.w3.org/ns/activitystreams#Public',
} as const;
};
return follow;
}

View file

@ -1,3 +1,5 @@
import config from '@/config/index.js';
import { getJson } from '@/misc/fetch.js';
import { ILocalUser } from '@/models/entities/user.js';
import { getInstanceActor } from '@/services/instance-actor.js';
import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
@ -11,7 +13,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
import { signedGet } from './request.js';
import { getApId, IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
import { parseUri } from './db-resolver.js';
/**
@ -84,18 +86,11 @@ export class Resolver {
const object = await signedGet(value, this.user);
if (
object == null
|| // check that this is an activitypub object by looking at the @context
(
Array.isArray(object['@context']) ?
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)
// Did we actually get the object that corresponds to the canonical URL?
// Does the host we requested stuff from actually correspond to the host that owns the activity?
|| !(getApId(object) == null || getApId(object) === value)
) {
if (object == null || (
Array.isArray(object['@context']) ?
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)) {
throw new Error('invalid response');
}

View file

@ -2,13 +2,14 @@ import { URL } from 'node:url';
import chalk from 'chalk';
import { IsNull } from 'typeorm';
import { DAY } from '@/const.js';
import config from '@/config/index.js';
import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
import webFinger from './webfinger.js';
import { createPerson, updatePerson } from './activitypub/models/person.js';
import { remoteLogger } from './logger.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
const logger = remoteLogger.createSubLogger('resolve-user');

View file

@ -5,12 +5,23 @@ import authenticate, { AuthenticationError } from './authenticate.js';
import call from './call.js';
import { ApiError } from './error.js';
function getRequestArguments(ctx: Koa.Context): Record<string, any> {
const args = {
...(ctx.params || {}),
...ctx.query,
...(ctx.request.body || {}),
};
// For security reasons, we drop the i parameter if it's a GET request
if (ctx.method === 'GET') {
delete args['i'];
}
return args;
}
export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<void> {
const body = ctx.is('multipart/form-data')
? (ctx.request as any).body
: ctx.method === 'GET'
? ctx.query
: ctx.request.body;
const body = getRequestArguments(ctx);
const error = (e: ApiError): void => {
ctx.status = e.httpStatusCode;

View file

@ -1,7 +1,8 @@
import * as crypto from 'node:crypto';
import Koa from 'koa';
import { IsNull, Not } from 'typeorm';
import { Apps, AuthSessions } from '@/models/index.js';
import { Apps, AuthSessions, AccessTokens } from '@/models/index.js';
import config from '@/config/index.js';
import { compareUrl } from './compare-url.js';
export async function oauth(ctx: Koa.Context): void {

View file

@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import Ajv from 'ajv';
import { CacheableLocalUser } from '@/models/entities/user.js';
import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
import { Schema, SchemaType } from '@/misc/schema.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { IEndpointMeta } from './endpoints.js';

View file

@ -702,6 +702,24 @@ export interface IEndpointMeta {
* (Cache-Control: public)
*/
readonly cacheSec?: number;
/**
* API v2 options
*/
readonly v2?: {
/**
* HTTP verb this endpoint supports
*/
readonly method: 'get' | 'put' | 'post' | 'patch' | 'delete';
/**
* Path alias for v2 endpoint
*
* @example (v0) /api/notes/create -> /api/v2/notes
*/
readonly alias?: string;
};
}
export interface IEndpoint {

View file

@ -114,8 +114,8 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined):
return await mergePack(
me,
isActor(object) ? await createPerson(object, resolver) : null,
isPost(object) ? await createNote(object, resolver, true) : null,
isActor(object) ? await createPerson(getApId(object), resolver) : null,
isPost(object) ? await createNote(getApId(object), resolver, true) : null,
);
}

View file

@ -1,5 +1,6 @@
import { Apps } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { unique } from '@/prelude/array.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { kinds } from '@/misc/api-permissions.js';
import define from '../../define.js';

View file

@ -32,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
token: ps.token,
});
if (result.affected === 0) {
if (result.affected == 0) {
throw new ApiError(meta.errors.noSuchSession);
}
});

View file

@ -1,4 +1,4 @@
import { Apps, AuthSessions, Users } from '@/models/index.js';
import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';

View file

@ -233,6 +233,10 @@ export const meta = {
},
},
},
v2: {
method: 'get'
},
} as const;
export const paramDef = {

View file

@ -21,6 +21,11 @@ export const meta = {
ref: 'Note',
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/children',
},
} as const;
export const paramDef = {

View file

@ -19,6 +19,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/clips',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -19,6 +19,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/conversation',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -37,6 +37,11 @@ export const meta = {
},
},
v2: {
method: 'post',
alias: 'notes',
},
errors: ['NO_SUCH_NOTE', 'PURE_RENOTE', 'EXPIRED_POLL', 'NO_SUCH_CHANNEL', 'BLOCKED', 'LESS_RESTRICTIVE_VISIBILITY'],
} as const;

View file

@ -18,6 +18,11 @@ export const meta = {
minInterval: SECOND,
},
v2: {
method: 'delete',
alias: 'notes/:noteId',
},
errors: ['ACCESS_DENIED', 'NO_SUCH_NOTE'],
} as const;

View file

@ -18,6 +18,10 @@ export const meta = {
ref: 'Note',
},
},
v2: {
method: 'get',
}
} as const;
export const paramDef = {

View file

@ -23,6 +23,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/reactions/:type?',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -22,6 +22,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/renotes',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -1,11 +1,10 @@
import { Notes } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['notes'],
@ -22,6 +21,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/replies',
},
errors: ['NO_SUCH_NOTE'],
} as const;
@ -38,7 +42,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
await getNote(ps.noteId, user).catch(err => {
const note = await getNote(ps.noteId, user).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
throw err;
});

View file

@ -14,6 +14,11 @@ export const meta = {
ref: 'Note',
},
v2: {
method: 'get',
alias: 'notes/:noteId',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -1,7 +1,7 @@
import { NoteFavorites, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import { getNote } from '../../common/getters.js';
import define from '../../define.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['notes'],
@ -27,6 +27,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/status',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -56,6 +56,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/translate/:targetLang/:sourceLang?',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View file

@ -18,6 +18,11 @@ export const meta = {
minInterval: SECOND,
},
v2: {
method: 'delete',
alias: 'notes/:noteId/renotes',
},
errors: ['NO_SUCH_NOTE'],
} as const;

Some files were not shown because too many files have changed in this diff Show more