From 139ccd258045183658874229d1fa09466ddc8dbc Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 23 Feb 2023 02:28:47 -0500 Subject: [PATCH 01/93] docs: Add Docker install guide Based on Misskey's Docker guide: https://github.com/misskey-dev/misskey-hub/blob/main/src/en/docs/install/docker.md --- docs/install-docker.md | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/install-docker.md diff --git a/docs/install-docker.md b/docs/install-docker.md new file mode 100644 index 000000000..91a95d59d --- /dev/null +++ b/docs/install-docker.md @@ -0,0 +1,70 @@ +# Create FoundKey instance with Docker Compose + +This guide describes how to install and setup FoundKey with Docker Compose. + +**WARNING:** +Never change the domain name (hostname) of an instance once you start using it! + + +## Requirements +- Docker or Podman +- Docker Compose plugin (or podman-compose) + +If using Podman, replace `docker` with `podman`. Commands using `docker compose` should be replaced with `podman-compose`. + +You may need to prefix `docker` commands with `sudo` unless your user is in the `docker` group or you are running Docker in rootless mode. + +## Get the repository +```sh +git clone https://akkoma.dev/FoundKeyGang/FoundKey.git +cd FoundKey +``` + +##Configure + +Copy example configuration files with following: + +```sh +cp .config/docker_example.yml .config/default.yml +cp .config/docker_example.env .config/docker.env +cp ./docker-compose.yml.example ./docker-compose.yml +``` + +Edit `default.yml` and `docker.env` according to the instructions in the files. + +Edit `docker-compose.yml` if necessary. (e.g. if you want to change the port). + +## Build and initialize +The following command will build FoundKey and initialize the database. +This will take some time. + +``` shell +docker compose build +docker compose run --rm web pnpm run init +``` + +## Launch +You can start FoundKey with the following command: + +```sh +docker compose up -d +``` + +## How to update your FoundKey server +When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyGang/FoundKey/src/branch/main/CHANGELOG.md) to know in advance the changes and whether or not additional work is required (in most cases, it is not). + +```sh +git stash +git checkout master +git pull +git stash pop +docker compose build +docker compose stop && docker compose up -d +``` + +It may take some time depending on the contents of the update and the size of the database. + +## How to execute CLI commands +```sh +docker compose run --rm web node packages/backend/built/tools/foo bar +``` From ecbe984315dec4048798b412f9a13489e7cf6758 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 23 Feb 2023 15:05:06 -0500 Subject: [PATCH 02/93] Add suggestions from @EpicKitty --- docs/install-docker.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 91a95d59d..39aeba710 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -20,7 +20,7 @@ git clone https://akkoma.dev/FoundKeyGang/FoundKey.git cd FoundKey ``` -##Configure +## Configure Copy example configuration files with following: @@ -31,6 +31,8 @@ cp ./docker-compose.yml.example ./docker-compose.yml ``` Edit `default.yml` and `docker.env` according to the instructions in the files. +You will need to set the database host to `db` and Redis host to `redis` in order to use the internal container network for these services. + Edit `docker-compose.yml` if necessary. (e.g. if you want to change the port). @@ -40,7 +42,7 @@ This will take some time. ``` shell docker compose build -docker compose run --rm web pnpm run init +docker compose run --rm web yarn run init ``` ## Launch @@ -50,6 +52,8 @@ You can start FoundKey with the following command: docker compose up -d ``` +In case you are encountering issues, you can run `docker compose logs -f` to get the log output of the running containers. + ## How to update your FoundKey server When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyGang/FoundKey/src/branch/main/CHANGELOG.md) to know in advance the changes and whether or not additional work is required (in most cases, it is not). From b046718d1d5c393fb082f7c0b8b902a268f2f7dc Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Fri, 24 Feb 2023 11:21:26 -0500 Subject: [PATCH 03/93] Use `docker compose down` instead of `stop` --- docs/install-docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 39aeba710..575ba4906 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -63,7 +63,7 @@ git checkout master git pull git stash pop docker compose build -docker compose stop && docker compose up -d +docker compose down && docker compose up -d ``` It may take some time depending on the contents of the update and the size of the database. From 5ffcb9f2352700a46d27503b6f23f3f663c0aa1c Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Fri, 24 Feb 2023 11:23:52 -0500 Subject: [PATCH 04/93] remove docker-compose.yml.example command --- docs/install-docker.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 575ba4906..9588df3d0 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -27,7 +27,6 @@ Copy example configuration files with following: ```sh cp .config/docker_example.yml .config/default.yml cp .config/docker_example.env .config/docker.env -cp ./docker-compose.yml.example ./docker-compose.yml ``` Edit `default.yml` and `docker.env` according to the instructions in the files. From 5662635d454a058495cf2a0ef2ba84949ed37246 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Wed, 22 Mar 2023 18:34:15 -0400 Subject: [PATCH 05/93] Update branch checked out to "main" --- docs/install-docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 9588df3d0..1e88454d9 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -58,7 +58,7 @@ When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyG ```sh git stash -git checkout master +git checkout main git pull git stash pop docker compose build From da246ce41956301d21a0590e5810fd4688f65cb7 Mon Sep 17 00:00:00 2001 From: Norm Date: Thu, 23 Mar 2023 16:34:01 +0000 Subject: [PATCH 06/93] docs: Update install guide to use tag-merge strategy (#117) Adapted from @toast 's guide: https://mk.toast.cafe/notes/93o9n4tfsi Reviewed-on: https://akkoma.dev/FoundKeyGang/FoundKey/pulls/117 --- docs/install.md | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/docs/install.md b/docs/install.md index f55cf0046..055c5a590 100644 --- a/docs/install.md +++ b/docs/install.md @@ -38,22 +38,29 @@ Create a separate non-root user to run FoundKey: adduser --disabled-password --disabled-login foundkey ``` +The following steps will require logging into the `foundkey` user, so do that now. +```sh +su - foundkey +``` + ## Install FoundKey -1. Login to the `foundkey` user +We recommend using a local branch and merging in upstream releases as they get tagged. This allows for easy local customization of your install. - `su - foundkey` +First, clone the FoundKey repo: +```sh +git clone https://akkoma.dev/FoundKeyGang/FoundKey +cd FoundKey +``` -2. Clone the FoundKey repository +Now create your local branch. In this example, we'll be using `toast.cafe` as the local branch name and release `v13.0.0-preview1` as the tag to track. To create that branch: +```sh +git checkout tags/v13.0.0-preview1 -b toast.cafe +``` - `git clone --recursive https://akkoma.dev/FoundKeyGang/FoundKey foundkey` - -3. Navigate to the repository - - `cd foundkey` - -4. Install FoundKey's dependencies - - `yarn install` +Updating will be covered in a later section. For now you'll want to install the dependencies using Yarn: +```sh +yarn install +``` ## Configure FoundKey 1. Copy `.config/example.yml` to `.config/default.yml`. @@ -185,14 +192,22 @@ rc-service foundkey start You can check if the service is running with `rc-service foundkey status`. ### Updating FoundKey -Use git to pull in the latest changes and rerun the build and migration commands: - +When a new release comes out, simply fetch and merge in the new tag. If you plan on making additional changes on top of that tag, we suggest using the `--squash` option with `git merge`. +```sh +git fetch -t +git merge tags/v13.0.0-preview2 +# you are now on the "next" release +``` + +Now you'll want to update your dependencies and rebuild: ```sh -git pull -git submodule update --init yarn install # Use build-parallel if your system has 4GB or more RAM and want faster builds NODE_ENV=production yarn build +``` + +Next, run the database migrations: +```sh yarn migrate ``` From c8a07e58f80a0d8e4a62b299278c480e76aacdd5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 23 Mar 2023 20:32:31 +0100 Subject: [PATCH 07/93] server: update summaly - updates got (CVE-2022-33987) - now properly preview XHTML pages Changelog: Security --- packages/backend/package.json | 2 +- yarn.lock | 38 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 197ddd605..66afa14c3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -99,7 +99,7 @@ "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "style-loader": "3.3.1", - "summaly": "2.6.0", + "summaly": "2.7.0", "syslog-pro": "1.0.0", "systeminformation": "5.11.22", "tinycolor2": "1.4.2", diff --git a/yarn.lock b/yarn.lock index 0eedb9ace..0a679b247 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1428,10 +1428,10 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^3.0.0": - version: 3.1.2 - resolution: "@sindresorhus/is@npm:3.1.2" - checksum: 6b68b2c0bc36beda9442c64e40e2e971999b0814af610a52d5c0bda2213061ff63d158912bd494dc8b8fa5c027ed13ec5947e4902dd9a315b2f2337221dbcb7f +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 83839f13da2c29d55c97abc3bc2c55b250d33a0447554997a85c539e058e57b8da092da396e252b11ec24a0279a0bed1f537fa26302209327060643e327f81d2 languageName: node linkType: hard @@ -3798,7 +3798,7 @@ __metadata: strict-event-emitter-types: 2.0.0 stringz: 2.1.0 style-loader: 3.3.1 - summaly: 2.6.0 + summaly: 2.7.0 syslog-pro: 1.0.0 systeminformation: 5.11.22 tinycolor2: 1.4.2 @@ -4281,7 +4281,7 @@ __metadata: languageName: node linkType: hard -"cacheable-request@npm:^7.0.1, cacheable-request@npm:^7.0.2": +"cacheable-request@npm:^7.0.2": version: 7.0.2 resolution: "cacheable-request@npm:7.0.2" dependencies: @@ -8364,22 +8364,22 @@ __metadata: languageName: node linkType: hard -"got@npm:11.5.1": - version: 11.5.1 - resolution: "got@npm:11.5.1" +"got@npm:11.8.5": + version: 11.8.5 + resolution: "got@npm:11.8.5" dependencies: - "@sindresorhus/is": ^3.0.0 + "@sindresorhus/is": ^4.0.0 "@szmarczak/http-timer": ^4.0.5 "@types/cacheable-request": ^6.0.1 "@types/responselike": ^1.0.0 cacheable-lookup: ^5.0.3 - cacheable-request: ^7.0.1 + cacheable-request: ^7.0.2 decompress-response: ^6.0.0 - http2-wrapper: ^1.0.0-beta.5.0 + http2-wrapper: ^1.0.0-beta.5.2 lowercase-keys: ^2.0.0 p-cancelable: ^2.0.0 responselike: ^2.0.0 - checksum: 3be52e602feb62a810de1337f92a054f49f5e74f8ee27ad4140331b63a99aeaddaeb8ec49b2dae493622f11ad55c7ee4bde5a7808147cc354f93b8c389957828 + checksum: 2de8a1bbda4e9b6b2b72b2d2100bc055a59adc1740529e631f61feb44a8b9a1f9f8590941ed9da9df0090b6d6d0ed8ffee94cd9ac086ec3409b392b33440f7d2 languageName: node linkType: hard @@ -8874,7 +8874,7 @@ __metadata: languageName: node linkType: hard -"http2-wrapper@npm:^1.0.0-beta.5.0": +"http2-wrapper@npm:^1.0.0-beta.5.2": version: 1.0.3 resolution: "http2-wrapper@npm:1.0.3" dependencies: @@ -16022,14 +16022,14 @@ __metadata: languageName: node linkType: hard -"summaly@npm:2.6.0": - version: 2.6.0 - resolution: "summaly@npm:2.6.0" +"summaly@npm:2.7.0": + version: 2.7.0 + resolution: "summaly@npm:2.7.0" dependencies: cheerio: 0.22.0 debug: 4.3.3 escape-regexp: 0.0.1 - got: 11.5.1 + got: 11.8.5 html-entities: 2.3.2 iconv-lite: 0.6.3 jschardet: 3.0.0 @@ -16037,7 +16037,7 @@ __metadata: private-ip: 2.3.3 require-all: 3.0.0 trace-redirect: 1.0.6 - checksum: 5eff928fc66dc9c2845b43f4242e1f5d37666ef829a6c0bd1ac6467ab91ff92e90eb53f4166c67c573c136a6f512310e34ab6a1acb866418dba8c2c6590dd139 + checksum: f5d5b845328ff80ab74229a08816990062153ac15b6581123c62283ecfa32c03ccfa446c3985285d3cfd3108868ab24f5c2bfc939ed781830148e0a39e03018e languageName: node linkType: hard From 3311bd866b23e03354b3e92fc55d634399be8d02 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 20 May 2022 09:28:57 +0200 Subject: [PATCH 08/93] add move notification type --- locales/en-US.yml | 2 + .../src/models/entities/notification.ts | 40 +++++++++++++------ .../src/models/repositories/notification.ts | 3 ++ packages/foundkey-js/src/consts.ts | 2 +- packages/foundkey-js/src/entities.ts | 5 +++ 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 887d7d287..9b53f3fc0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1293,6 +1293,7 @@ _notification: yourFollowRequestAccepted: "Your follow request was accepted" youWereInvitedToGroup: "{userName} invited you to a group" pollEnded: "Poll results have become available" + moved: "{name} has moved to a different account" emptyPushNotificationMessage: "Push notifications have been updated" _types: follow: "New followers" @@ -1306,6 +1307,7 @@ _notification: receiveFollowRequest: "Received follow requests" followRequestAccepted: "Accepted follow requests" groupInvited: "Group invitations" + move: "Others moving accounts" app: "Notifications from linked apps" _actions: followBack: "followed you back" diff --git a/packages/backend/src/models/entities/notification.ts b/packages/backend/src/models/entities/notification.ts index cab6551f7..a0d6cbde4 100644 --- a/packages/backend/src/models/entities/notification.ts +++ b/packages/backend/src/models/entities/notification.ts @@ -52,19 +52,20 @@ export class Notification { public notifier: User | null; /** - * 通知の種類。 - * follow - フォローされた - * mention - 投稿で自分が言及された - * reply - (自分または自分がWatchしている)投稿が返信された - * renote - (自分または自分がWatchしている)投稿がRenoteされた - * quote - (自分または自分がWatchしている)投稿が引用Renoteされた - * reaction - (自分または自分がWatchしている)投稿にリアクションされた - * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された - * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した - * receiveFollowRequest - フォローリクエストされた - * followRequestAccepted - 自分の送ったフォローリクエストが承認された - * groupInvited - グループに招待された - * app - アプリ通知 + * Type of notification. + * follow - notifier followed notifiee + * mention - notifiee was mentioned + * reply - notifiee (author or watching) was replied to + * renote - notifiee (author or watching) was renoted + * quote - notifiee (author or watching) was quoted + * reaction - notifiee (author or watching) had a reaction added to the note + * pollVote - new vote in a poll notifiee authored or watched + * pollEnded - notifiee's poll ended + * receiveFollowRequest - notifiee received a new follow request + * followRequestAccepted - notifier accepted notifees follow request + * groupInvited - notifiee was invited into a group + * move - notifier moved + * app - custom application notification */ @Index() @Column('enum', { @@ -129,6 +130,19 @@ export class Notification { }) public choice: number | null; + @Column({ + ...id(), + nullable: true, + comment: 'The ID of the moved to account.', + }) + public moveTargetId: User['id'] | null; + + @ManyToOne(() => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public moveTarget: User | null; + /** * アプリ通知のbody */ diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index c62a24959..c1e2273be 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -44,6 +44,9 @@ export const NotificationRepository = db.getRepository(Notification).extend({ ...(notification.type === 'groupInvited' ? { invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!), } : {}), + ...(notification.type === 'move' ? { + moveTarget: Users.pack(notification.moveTarget ?? notification.moveTargetId), + } : {}), ...(notification.type === 'app' ? { body: notification.customBody, header: notification.customHeader || token?.name, diff --git a/packages/foundkey-js/src/consts.ts b/packages/foundkey-js/src/consts.ts index a90b8720a..a7ea18d25 100644 --- a/packages/foundkey-js/src/consts.ts +++ b/packages/foundkey-js/src/consts.ts @@ -1,4 +1,4 @@ -export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const; +export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app'] as const; export const noteNotificationTypes = ['mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded'] as const; diff --git a/packages/foundkey-js/src/entities.ts b/packages/foundkey-js/src/entities.ts index 30dd90582..c8a91fba9 100644 --- a/packages/foundkey-js/src/entities.ts +++ b/packages/foundkey-js/src/entities.ts @@ -223,6 +223,11 @@ export type Notification = { invitation: UserGroup; user: User; userId: User['id']; +} | { + type: 'move', + user: User; + userId: User['id']; + moveTarget: User; } | { type: 'app'; header?: string | null; From 3c2092935c9d12adbd4a75b4e78eb2c9857c4028 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 20 May 2022 10:36:14 +0200 Subject: [PATCH 09/93] server: add object hint to resolvePerson --- packages/backend/src/remote/activitypub/models/person.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 803fd03c9..ba677a9dc 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -376,7 +376,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver): * If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it. * Fetch the person from the remote server, register it in FoundKey, and return it. */ -export async function resolvePerson(uri: string, resolver: Resolver): Promise { +export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す @@ -388,7 +388,7 @@ export async function resolvePerson(uri: string, resolver: Resolver): Promise Date: Fri, 20 May 2022 10:38:29 +0200 Subject: [PATCH 10/93] server: implement receiving Move activities For now only creates notifications. --- packages/backend/src/models/entities/user.ts | 14 ++++- .../src/remote/activitypub/kernel/index.ts | 5 +- .../remote/activitypub/kernel/move/index.ts | 62 +++++++++++++++++++ .../backend/src/remote/activitypub/type.ts | 5 ++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/remote/activitypub/kernel/move/index.ts diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 0aee4c90b..6cfca187a 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -1,4 +1,4 @@ -import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; +import { Entity, Column, Index, OneToOne, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm'; import { id } from '../id.js'; import { DriveFile } from './drive-file.js'; @@ -230,6 +230,18 @@ export class User { }) public federateBlocks: boolean; + @Column({ + ...id(), + nullable: true, + }) + public movedToId: User['id'] | null; + + @ManyToOne(() => User, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public movedTo: User | null; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index 79e2dce75..46a972a7e 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -4,7 +4,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js'; import { extractDbHost } from '@/misc/convert-host.js'; import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import { apLogger } from '../logger.js'; -import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag, getApId } from '../type.js'; +import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag, isMove, getApId } from '../type.js'; import create from './create/index.js'; import performDeleteActivity from './delete/index.js'; import performUpdateActivity from './update/index.js'; @@ -19,6 +19,7 @@ import add from './add/index.js'; import remove from './remove/index.js'; import block from './block/index.js'; import flag from './flag/index.js'; +import { move } from './move/index.js'; export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { if (isCollectionOrOrderedCollection(activity)) { @@ -73,6 +74,8 @@ async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, await block(actor, activity); } else if (isFlag(activity)) { await flag(actor, activity); + } else if (isMove(activity)) { + await move(actor, activity, resolver); } else { apLogger.warn(`unrecognized activity type: ${(activity as any).type}`); } diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts new file mode 100644 index 000000000..e64656e09 --- /dev/null +++ b/packages/backend/src/remote/activitypub/kernel/move/index.ts @@ -0,0 +1,62 @@ +import { IsNull } from 'typeorm'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { resolvePerson } from '@/remote/activitypub/models/person.js'; +import { Followings, Users } from '@/models/index.js'; +import { createNotification } from '@/services/create-notification.js'; +import Resolver from '../../resolver.js'; +import { IMove, isActor, getApId } from '../../type.js'; + +export async function move(actor: CacheableRemoteUser, activity: IMove, resolver: Resolver): Promise { + // actor is not move origin + if (activity.object == null || getApId(activity.object) !== actor.uri) return; + + // actor already moved + if (actor.movedTo != null) return; + + // no move target + if (activity.target == null) return; + + /* the database resolver can not be used here, because: + * 1. It must be ensured that the latest data is used. + * 2. The AP representation is needed, because `alsoKnownAs` + * is not stored in the database. + * This also checks whether the move target is blocked + */ + const movedToAp = await resolver.resolve(getApId(activity.target)); + + // move target is not an actor + if (!isActor(movedToAp)) return; + + // move destination has not accepted + if (!Array.isArray(movedToAp.alsoKnownAs) || !movedToAp.alsoKnownAs.includes(actor.id)) return; + + // ensure the user exists + const movedTo = await resolvePerson(getApId(activity.target), resolver, movedToAp); + // move target is already suspended + if (movedTo.isSuspended) return; + + // process move for local followers + const followings = Followings.find({ + select: { + followerId: true, + }, + where: { + followeeId: actor.id, + followerHost: IsNull(), + }, + }); + + await Promise.all([ + Users.update(actor.id, { + movedToId: movedTo.id, + }), + ...followings.map(async (following) => { + // TODO: autoAcceptMove? + + await createNotification(following.followerId, 'move', { + notifierId: actor.id, + moveTargetId: movedTo.id, + }); + }), + ]); +} diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 6298d44d8..8a46ac594 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -296,6 +296,10 @@ export interface IFlag extends IActivity { type: 'Flag'; } +export interface IMove extends IActivity { + type: 'Move'; +} + export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; @@ -310,6 +314,7 @@ export const isLike = (object: IObject): object is ILike => getApType(object) == export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; +export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move'; export interface ILink { href: string; From 5ad18c8626681ec5983c52d1bfe3a1cdb9bc9d75 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 20 Nov 2022 22:05:16 +0100 Subject: [PATCH 11/93] server: add migration for movedTo user/notif --- .../migration/1668977715500-movedTo.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/backend/migration/1668977715500-movedTo.js diff --git a/packages/backend/migration/1668977715500-movedTo.js b/packages/backend/migration/1668977715500-movedTo.js new file mode 100644 index 000000000..721e01743 --- /dev/null +++ b/packages/backend/migration/1668977715500-movedTo.js @@ -0,0 +1,35 @@ +export class movedTo1668977715500 { + name = 'movedTo1668977715500'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "movedToId" character varying(32)`); + await queryRunner.query(`ALTER TABLE "notification" ADD "moveTargetId" character varying(32)`); + await queryRunner.query(`COMMENT ON COLUMN "notification"."moveTargetId" IS 'The ID of the moved to account.'`); + await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_16fef167e4253ccdc8971b01f6e" FOREIGN KEY ("movedToId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_078db271ad52ccc345b7b2b026a" FOREIGN KEY ("moveTargetId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TYPE "notification_type_enum" ADD VALUE 'move'`); + await queryRunner.query(`ALTER TYPE "user_profile_mutingnotificationtypes_enum" ADD VALUE 'move'`); + } + + async down(queryRunner) { + // remove 'move' from user muting notifications type enum + await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'pollEnded')`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`); + await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`); + await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`); + + // remove 'move' from notification type enum + await queryRunner.query(`DELETE FROM "notification" WHERE "type" = 'move'`); + await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`); + await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`); + await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`); + + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_078db271ad52ccc345b7b2b026a"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_16fef167e4253ccdc8971b01f6e"`); + await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "moveTargetId"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedToId"`); + } +} From 72b8489ae759368d0624fa50f39babeb13afdae0 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 20 Nov 2022 20:06:03 +0100 Subject: [PATCH 12/93] client: display move notification --- .../notification-badges/suitcase-solid.png | Bin 0 -> 1123 bytes packages/client/src/components/notification.vue | 5 +++++ packages/sw/src/scripts/create-notification.ts | 14 ++++++++++++++ packages/sw/src/sw.ts | 3 +++ 4 files changed, 22 insertions(+) create mode 100644 packages/backend/assets/notification-badges/suitcase-solid.png diff --git a/packages/backend/assets/notification-badges/suitcase-solid.png b/packages/backend/assets/notification-badges/suitcase-solid.png new file mode 100644 index 0000000000000000000000000000000000000000..b52688586cc00f1d224b397871906acb291f0c1b GIT binary patch literal 1123 zcmeAS@N?(olHy`uVBq!ia0vp^DIm~}U&3=GU4JY5_^D(1Ys>)$b@ROa}{ z@86fV?u=TOn*Gvo&yprF&j(Arr>JfXx~OQ=#^R{bv7(f7VRvkk!v`tFQjO%r&FrU- z2LCtP$ahs$D<;^mDQr#X$JdEv?&Wi5);_uU^Y6abHE{;*b1T#8?$0zgw*G9>e*Dhd zpffp=AD9d{Y*^MXh^5IrRx;rF!&Spq^X%Po(B4ZxTcV{jq8V$#EESgw&@MN@BX>3V+~%{dS*@k zvGgx7os1h~B#bua{J*}5mGK+HYvXn1(Q<5MPos2ByI(u9edYZI*Vy;ug1`MlO?PC^P{>aS@87-u@|TM4jF}gIEWCBHOJ?(vUngFz-f(v1I{%CJ=8JAy`AH`0^v|u~ zS^*OhL{ubLIo%wa0w*MB{pT~>?0UfKLEVFMVzd7|ey41j7MY&re{Dy-?CV!|Zk{n? z`m39_i)9Y8-_GByUi@ZA`_-n$Kf39T7+lcn#nzbSYVS2Lj?r-G!{y+86);kwI zn&xjlko7?M!7E*!H40bt!W*3rtP1Ndz5HLmxSH(;V};~~m)pH_*>afcOg68*oIgkC z$eC@-@eJRe%v`%%myK`sO>OhIn>UdSM%iYrsf>%e!1xy9}$-~8aSn7F6# z+`QPO%LVV1l>0Nhv*W3WD4RF$TjTC}&ft29eOxucY4hfpG3_z?>2BPy`@jEVAI{_4 zJXcq@H7>vVcmGN4WpAtszkfaueBk$iscRO0ou+-D`aoLw|KG=?^KMOl`fmE2TJ{Z$ zKc*bJsg+vR!#tni_1)U#yW{8WbG#kAe%J5w2fWkD%r~%B@I>(OY}tAGBww@4TANRI zOq_oEPuYAx@ + {{ i18n.ts.reject }} + + {{ i18n.ts.moved }} +
+
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 9d5e31335..873840943 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -217,6 +217,20 @@ async function composeNotification(data ], }]; + case 'move': + return [t('_notification.moved', { name: getUserName(data.body.user) }), { + body: getUserName(data.body.moveTarget), + icon: data.body.moveTarget.avatarUrl, + badge: iconUrl('suitcase'), + data, + action: [ + { + action: 'accept', + title: t('follow'), + }, + ], + }]; + case 'app': return [data.body.header || data.body.body, { body: data.body.header && data.body.body, diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 10cfc69d3..67846cea6 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -96,6 +96,9 @@ self.addEventListener('notificationclick', Date: Sun, 20 Nov 2022 21:21:12 +0100 Subject: [PATCH 13/93] server: implement moveTo property on actors Co-authored-by: Mary Strodl Co-authored-by: amybones --- .../src/remote/activitypub/models/person.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index ba677a9dc..17d9858e4 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -39,7 +39,7 @@ const summaryLength = 2048; * @param x Fetched object * @param uri Fetch target URI */ -function validateActor(x: IObject): IActor { +async function validateActor(x: IObject, resolver: Resolver): Promise { if (x == null) { throw new Error('invalid Actor: object is null'); } @@ -61,6 +61,22 @@ function validateActor(x: IObject): IActor { throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); } + if (x.movedTo !== undefined) { + if (!(typeof x.movedTo === 'string' && x.movedTo.length > 0)) { + throw new Error('invalid Actor: wrong movedTo'); + } + if (x.movedTo === uri) { + throw new Error('invalid Actor: moved to self'); + } + // This may throw an exception if we cannot resolve the move target. + // If we are processing an incoming activity, this is desired behaviour + // because that will cause the activity to be retried. + await resolvePerson(x.movedTo, resolver) + .then(moveTarget => { + x.movedTo = moveTarget.id + }); + } + if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { throw new Error('invalid Actor: wrong inbox'); } @@ -137,7 +153,7 @@ export async function fetchPerson(uri: string): Promise { export async function createPerson(value: string | IObject, resolver: Resolver): Promise { const object = await resolver.resolve(value) as any; - const person = validateActor(object); + const person = await validateActor(object, resolver); apLogger.info(`Creating the Person: ${person.id}`); @@ -177,6 +193,7 @@ export async function createPerson(value: string | IObject, resolver: Resolver): isBot, isCat: (person as any).isCat === true, showTimelineReplies: false, + movedToId: person.movedTo, })) as IRemoteUser; await transactionalEntityManager.save(new UserProfile({ @@ -287,7 +304,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver): const object = await resolver.resolve(value); - const person = validateActor(object); + const person = await validateActor(object, resolver); apLogger.info(`Updating the Person: ${person.id}`); @@ -328,6 +345,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver): isCat: (person as any).isCat === true, isLocked: !!person.manuallyApprovesFollowers, isExplorable: !!person.discoverable, + movedToId: person.movedTo, } as Partial; if (avatar) { From c1f7ad0c1424d6509b557b4b624c74ce4d0f841b Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 22 Nov 2022 15:55:59 +0100 Subject: [PATCH 14/93] server: add movedTo to packed user --- packages/backend/src/models/repositories/user.ts | 4 ++++ packages/foundkey-js/src/entities.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 060bc0cd6..134a4dcbd 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -300,6 +300,10 @@ export const UserRepository = db.getRepository(User).extend({ }), emojis: populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), + movedTo: !user.movedToId ? undefined : this.pack(user.movedTo ?? user.movedToId, me, { + ...opts, + detail: false, + }), ...(opts.detail ? { url: profile!.url, diff --git a/packages/foundkey-js/src/entities.ts b/packages/foundkey-js/src/entities.ts index c8a91fba9..3742630bb 100644 --- a/packages/foundkey-js/src/entities.ts +++ b/packages/foundkey-js/src/entities.ts @@ -28,6 +28,7 @@ export type UserLite = { faviconUrl: Instance['faviconUrl']; themeColor: Instance['themeColor']; }; + movedTo?: UserLite; }; export type UserDetailed = UserLite & { From aa428bd1a475d8156272c230ce1b0c212040a01f Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 22 Nov 2022 16:17:06 +0100 Subject: [PATCH 15/93] client: display moved information on profile --- locales/en-US.yml | 1 + packages/client/src/pages/user/home.vue | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/locales/en-US.yml b/locales/en-US.yml index 9b53f3fc0..8a0c4e707 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -829,6 +829,7 @@ oauthErrorGoBack: "An error happened while trying to authenticate a 3rd party ap \ Please go back and try again." appAuthorization: "App authorization" noPermissionsRequested: "(No permissions requested.)" +movedTo: "This user has moved to {handle}." _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue index 2fcee6987..9236d1905 100644 --- a/packages/client/src/pages/user/home.vue +++ b/packages/client/src/pages/user/home.vue @@ -9,6 +9,16 @@
+ + + + + +
- {{ i18n.ts.moved }} + + +
From 9b7dcb4262ef118d5c15c6d147ce101027e6f2ac Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sat, 25 Mar 2023 01:42:33 -0400 Subject: [PATCH 17/93] Update instructions to match changes in da246ce41956301d21a0590e5810fd4688f65cb7 --- docs/install-docker.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 1e88454d9..6e9d14b92 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -20,6 +20,12 @@ git clone https://akkoma.dev/FoundKeyGang/FoundKey.git cd FoundKey ``` +To make it easier to perform your own changes on top, we suggest making a branch based on the latest tag. +In this example, we'll use `v13.0.0-preview1` as the tag to check out and `my-branch` as the branch name. +```sh +git checkout tags/v13.0.0-preview1 -b my-branch +``` + ## Configure Copy example configuration files with following: @@ -56,11 +62,15 @@ In case you are encountering issues, you can run `docker compose logs -f` to get ## How to update your FoundKey server When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyGang/FoundKey/src/branch/main/CHANGELOG.md) to know in advance the changes and whether or not additional work is required (in most cases, it is not). +To update your branch to the latest tag (in this example `v13.0.0-preview2`), you can do the following: ```sh -git stash -git checkout main -git pull -git stash pop +git fetch -t + +# Use --squash if you want to merge all of the changes in the tag into a single commit. +# Useful if you have made additional changes. +git merge tags/v13.0.0-preview2 + +# Rebuild and restart the docker container. docker compose build docker compose down && docker compose up -d ``` From 77602203b297a1ce71048ddf8deacdeea72af669 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 10:55:27 +0200 Subject: [PATCH 18/93] client: replace error UUIDs with error codes The error UUIDs were removed from the backend and trying to match against the IDs no longer works. This can produce confusing UI behaviour when displaying errors. closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/363 Changelog: Fixed --- packages/client/src/account.ts | 2 +- packages/client/src/components/drive.folder.vue | 4 ++-- packages/client/src/components/drive.vue | 8 ++++---- packages/client/src/components/signin.vue | 10 +++++----- packages/client/src/pages/page-editor.vue | 2 +- packages/client/src/scripts/get-note-menu.ts | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 88fc8387d..e39540e47 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -78,7 +78,7 @@ function fetchAccount(token: string): Promise { api('i', {}, token) .then(res => { if (res.error) { - if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { + if (res.error.code === 'SUSPENDED') { showSuspendedDialog().then(() => { signout(); }); diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index c613571f5..bfc3ffaf6 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -207,8 +207,8 @@ function deleteFolder() { defaultStore.set('uploadFolder', null); } }).catch(err => { - switch (err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + switch (err.code) { + case 'HAS_CHILD_FILES_OR_FOLDERS': os.alert({ type: 'error', title: i18n.ts.unableToDelete, diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index d39e256f8..a3b67a5b2 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -258,8 +258,8 @@ function onDrop(ev: DragEvent): any { folderId: droppedFolder.id, parentId: folder?.id ?? null, }).catch(err => { - switch (err) { - case 'detected-circular-definition': + switch (err.code) { + case 'RECURSIVE_FOLDER': os.alert({ title: i18n.ts.unableToProcess, text: i18n.ts.circularReferenceFolder, @@ -335,8 +335,8 @@ function deleteFolder(folderToDelete: foundkey.entities.DriveFolder) { // 削除時に親フォルダに移動 move(folderToDelete.parentId); }).catch(err => { - switch (err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + switch (err.code) { + case 'HAS_CHILD_FILES_OR_FOLDERS': os.alert({ type: 'error', title: i18n.ts.unableToDelete, diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue index 42f5bf8f0..8f0c5fe17 100644 --- a/packages/client/src/components/signin.vue +++ b/packages/client/src/components/signin.vue @@ -185,8 +185,8 @@ function onSubmit() { } function loginFailed(err) { - switch (err.id) { - case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { + switch (err.code) { + case 'NO_SUCH_USER': { os.alert({ type: 'error', title: i18n.ts.loginFailed, @@ -194,7 +194,7 @@ function loginFailed(err) { }); break; } - case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': { + case 'ACCESS_DENIED': { os.alert({ type: 'error', title: i18n.ts.loginFailed, @@ -202,11 +202,11 @@ function loginFailed(err) { }); break; } - case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { + case 'SUSPENDED': { showSuspendedDialog(); break; } - case '22d05606-fbcf-421a-a2db-b32610dcfd1b': { + case 'RATE_LIMIT_EXCEEDED': { os.alert({ type: 'error', title: i18n.ts.loginFailed, diff --git a/packages/client/src/pages/page-editor.vue b/packages/client/src/pages/page-editor.vue index 6f0af17ee..9763e6466 100644 --- a/packages/client/src/pages/page-editor.vue +++ b/packages/client/src/pages/page-editor.vue @@ -115,7 +115,7 @@ function save() { const options = getSaveOptions(); const onError = err => { - if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') { + if (err.code === 'INVALID_PARAM') { if (err.info.param === 'name') { os.alert({ type: 'error', diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 1afab0071..73b960cae 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -104,7 +104,7 @@ export function getNoteMenu(props: { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id, }, undefined, null, res => { - if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + if (res.code === 'PIN_LIMIT_EXCEEDED') { os.alert({ type: 'error', text: i18n.ts.pinLimitExceeded, @@ -149,7 +149,7 @@ export function getNoteMenu(props: { os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }), null, async (err) => { - if (err.id === '734806c4-542c-463a-9311-15c512803965') { + if (err.id === 'ALREADY_CLIPPED') { const confirm = await os.confirm({ type: 'warning', text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }), From 701054b86e80c0b4c8679548d251acf31b149ad2 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 10:57:12 +0200 Subject: [PATCH 19/93] replace NBSP with SP How did this get here in the first place? --- packages/backend/src/server/api/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 4af16383b..176144f3e 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -36,7 +36,7 @@ export class ApiError extends Error { break; case 429: if (typeof this.info === 'object' && typeof this.info.reset === 'number') { - ctx.respose.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000))); + ctx.respose.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000))); } break; } From 94d1cf75aa2e586bf7ae56dca4adbfd48fb1e520 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 25 Mar 2023 20:40:35 +0100 Subject: [PATCH 20/93] server: unify drive object types in database Minor adjustment: The 'name' columns have the same max length. Major adjustment: Rename both columns to be "parentId" and update all references of this name in the backend. API parameters are not changed, since that would be an unnecessary breaking change. --- .../1679767920029-unify-drive-objects.js | 22 +++++++++++++++++++ .../backend/src/models/entities/drive-file.ts | 6 ++--- .../src/models/entities/drive-folder.ts | 2 +- .../src/models/repositories/drive-file.ts | 4 ++-- .../src/models/repositories/drive-folder.ts | 2 +- .../src/server/api/endpoints/drive/files.ts | 4 ++-- .../api/endpoints/drive/files/create.ts | 2 +- .../server/api/endpoints/drive/files/find.ts | 2 +- .../api/endpoints/drive/files/update.ts | 6 ++--- .../endpoints/drive/files/upload-from-url.ts | 2 +- .../api/endpoints/drive/folders/delete.ts | 2 +- .../src/server/api/endpoints/drive/show.ts | 4 ++-- .../backend/src/services/drive/add-file.ts | 10 ++++----- .../src/services/drive/upload-from-url.ts | 6 ++--- 14 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 packages/backend/migration/1679767920029-unify-drive-objects.js diff --git a/packages/backend/migration/1679767920029-unify-drive-objects.js b/packages/backend/migration/1679767920029-unify-drive-objects.js new file mode 100644 index 000000000..b9bab31d9 --- /dev/null +++ b/packages/backend/migration/1679767920029-unify-drive-objects.js @@ -0,0 +1,22 @@ +export class unifyDriveObjects1679767920029 { + name = 'unifyDriveObjects1679767920029'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" RENAME COLUMN "folderId" TO "parentId"`); + await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "name" TYPE character varying(256)`); + // The column name changed so the name that typeorm generates for indices and foreign keys changes too. + // To avoid reindexing, just rename them. + await queryRunner.query(`ALTER TABLE "drive_file" RENAME CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a" TO "FK_84b4e3038e7e64a68764dd7ea3e"`); + await queryRunner.query(`ALTER INDEX "IDX_bb90d1956dafc4068c28aa7560" RENAME TO "IDX_84b4e3038e7e64a68764dd7ea3"`); + await queryRunner.query(`ALTER INDEX "IDX_55720b33a61a7c806a8215b825" RENAME TO "IDX_7c607687cd487292d16617b23e"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" RENAME CONSTRAINT "FK_84b4e3038e7e64a68764dd7ea3e" TO "FK_bb90d1956dafc4068c28aa7560a"`); + await queryRunner.query(`ALTER INDEX "IDX_84b4e3038e7e64a68764dd7ea3" RENAME TO "IDX_bb90d1956dafc4068c28aa7560"`); + await queryRunner.query(`ALTER INDEX "IDX_7c607687cd487292d16617b23e" RENAME TO "IDX_55720b33a61a7c806a8215b825"`); + + await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "name" TYPE character varying(128) USING substr("name", 1, 128)`); + await queryRunner.query(`ALTER TABLE "drive_file" RENAME COLUMN "parentId" TO "folderId"`); + } +} diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 8873b9440..cd7af7e14 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -4,7 +4,7 @@ import { User } from './user.js'; import { DriveFolder } from './drive-folder.js'; @Entity() -@Index(['userId', 'folderId', 'id']) +@Index(['userId', 'parentId', 'id']) export class DriveFile { @PrimaryColumn(id()) public id: string; @@ -142,13 +142,13 @@ export class DriveFile { nullable: true, comment: 'The parent folder ID. If null, it means the DriveFile is located in root.', }) - public folderId: DriveFolder['id'] | null; + public parentId: DriveFolder['id'] | null; @ManyToOne(() => DriveFolder, { onDelete: 'SET NULL', }) @JoinColumn() - public folder: DriveFolder | null; + public parent: DriveFolder | null; @Index() @Column('boolean', { diff --git a/packages/backend/src/models/entities/drive-folder.ts b/packages/backend/src/models/entities/drive-folder.ts index d7e323e72..bee7e397c 100644 --- a/packages/backend/src/models/entities/drive-folder.ts +++ b/packages/backend/src/models/entities/drive-folder.ts @@ -14,7 +14,7 @@ export class DriveFolder { public createdAt: Date; @Column('varchar', { - length: 128, + length: 256, comment: 'The name of the DriveFolder.', }) public name: string; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index ce3e7b705..bddde2d50 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -105,8 +105,8 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ url: opts.self ? file.url : this.getPublicUrl(file, false), thumbnailUrl: this.getPublicUrl(file, true), comment: file.comment, - folderId: file.folderId, - folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { + folderId: file.parentId, + folder: opts.detail && file.parentId ? DriveFolders.pack(file.parentId, { detail: true, }) : undefined, userId: file.userId, diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index bb744a4b7..0e61e4ff0 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -28,7 +28,7 @@ export const DriveFolderRepository = db.getRepository(DriveFolder).extend({ parentId: folder.id, }), filesCount: DriveFiles.countBy({ - folderId: folder.id, + parentId: folder.id, }), ...(folder.parentId ? { diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index b30ca78ad..5da30397b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -38,9 +38,9 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere('file.userId = :userId', { userId: user.id }); if (ps.folderId) { - query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + query.andWhere('file.parentId = :parentId', { parentId: ps.folderId }); } else { - query.andWhere('file.folderId IS NULL'); + query.andWhere('file.parentId IS NULL'); } if (ps.type) { diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 6862545f1..64598eb1a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -62,7 +62,7 @@ export default define(meta, paramDef, async (ps, user, _, file, cleanup) => { try { // Create file - const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive }); + const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, parentId: ps.folderId, force: ps.force, sensitive: ps.isSensitive }); return await DriveFiles.pack(driveFile, { self: true }); } catch (e) { if (e instanceof Error || typeof e === 'string') { diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 23069b810..8ec5de0a2 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -36,7 +36,7 @@ export default define(meta, paramDef, async (ps, user) => { const files = await DriveFiles.findBy({ name: ps.name, userId: user.id, - folderId: ps.folderId ?? IsNull(), + parentId: ps.folderId ?? IsNull(), }); return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 8325a428a..c84f51a30 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -54,7 +54,7 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.folderId !== undefined) { if (ps.folderId === null) { - file.folderId = null; + file.parentId = null; } else { const folder = await DriveFolders.findOneBy({ id: ps.folderId, @@ -63,14 +63,14 @@ export default define(meta, paramDef, async (ps, user) => { if (folder == null) throw new ApiError('NO_SUCH_FOLDER'); - file.folderId = folder.id; + file.parentId = folder.id; } } await DriveFiles.update(file.id, { name: file.name, comment: file.comment, - folderId: file.folderId, + parentId: file.parentId, isSensitive: file.isSensitive, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 484021b30..960f13a54 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -34,7 +34,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { + uploadFromUrl({ url: ps.url, user, parentId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { DriveFiles.pack(file, { self: true }).then(packedFile => { publishMainStream(user.id, 'urlUploadFinished', { marker: ps.marker, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index e6df8265c..6df6eb520 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { const [childFoldersCount, childFilesCount] = await Promise.all([ DriveFolders.countBy({ parentId: folder.id }), - DriveFiles.countBy({ folderId: folder.id }), + DriveFiles.countBy({ parentId: folder.id }), ]); if (childFoldersCount !== 0 || childFilesCount !== 0) { diff --git a/packages/backend/src/server/api/endpoints/drive/show.ts b/packages/backend/src/server/api/endpoints/drive/show.ts index 3d88ed5f5..5bc6ce783 100644 --- a/packages/backend/src/server/api/endpoints/drive/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/show.ts @@ -48,10 +48,10 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.folderId) { foldersQuery.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); - filesQuery.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + filesQuery.andWhere('file.parentId = :parentId', { parentId: ps.folderId }); } else { foldersQuery.andWhere('folder.parentId IS NULL'); - filesQuery.andWhere('file.folderId IS NULL'); + filesQuery.andWhere('file.parentId IS NULL'); } const folders = await foldersQuery.take(ps.limit).getMany(); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 81da49df8..9cdade177 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -322,7 +322,7 @@ type AddFileArgs = { /** Comment */ comment?: string | null; /** Folder ID */ - folderId?: any; + parentId?: any; /** If set to true, forcibly upload the file even if there is a file with the same hash. */ force?: boolean; /** Do not save file to local */ @@ -344,7 +344,7 @@ export async function addFile({ path, name = null, comment = null, - folderId = null, + parentId = null, force = false, isLink = false, url = null, @@ -392,12 +392,12 @@ export async function addFile({ //#endregion const fetchFolder = async (): Promise => { - if (!folderId) { + if (!parentId) { return null; } const driveFolder = await DriveFolders.findOneBy({ - id: folderId, + id: parentId, userId: user ? user.id : IsNull(), }); @@ -429,7 +429,7 @@ export async function addFile({ file.createdAt = new Date(); file.userId = user ? user.id : null; file.userHost = user ? user.host : null; - file.folderId = folder?.id ?? null; + file.parentId = folder?.id ?? null; file.comment = comment; file.properties = properties; file.blurhash = info.blurhash || null; diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index a815d8374..e8f3793b7 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -13,7 +13,7 @@ const logger = driveLogger.createSubLogger('downloader'); type Args = { url: string; user: { id: User['id']; host: User['host'] } | null; - folderId?: DriveFolder['id'] | null; + parentId?: DriveFolder['id'] | null; uri?: string | null; sensitive?: boolean; force?: boolean; @@ -24,7 +24,7 @@ type Args = { export async function uploadFromUrl({ url, user, - folderId = null, + parentId = null, uri = null, sensitive = false, force = false, @@ -50,7 +50,7 @@ export async function uploadFromUrl({ // If the comment is same as the name, skip comment // (image.name is passed in when receiving attachment) comment: name === comment ? null : comment, - folderId, + parentId, force, isLink, url, From 68f9e3e0dd45af0738801a254a6d47c1b501a218 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 25 Mar 2023 22:54:54 +0100 Subject: [PATCH 21/93] server: change pagination of drive/show endpoint This changes the pagination of the drive/show API endpoint to use the offset variant of pagination and allows to specify a sorting. closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/362 --- .../src/server/api/endpoints/drive/show.ts | 95 ++++++++++++++----- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/drive/show.ts b/packages/backend/src/server/api/endpoints/drive/show.ts index 5bc6ce783..54490be0a 100644 --- a/packages/backend/src/server/api/endpoints/drive/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/show.ts @@ -1,11 +1,13 @@ +import { In } from 'typeorm'; import { DriveFiles, DriveFolders } from '@/models/index.js'; import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['drive'], - description: "Lists all folders and files in the authenticated user's drive. Folders are always listed first. The limit, if specified, is applied over the total number of elements.", + description: "Lists all folders and files in the authenticated user's drive. Default sorting is folders first, then newest first. The limit, if specified, is applied over the total number of elements.", requireCredential: true, @@ -31,40 +33,83 @@ export const meta = { export const paramDef = { type: 'object', properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 30 + }, + offset: { + type: 'integer', + default: 0, + }, + sort: { + type: 'string', + enum: [ + '+created', + '-created', + '+name', + '-name', + ], + }, + folderId: { + type: 'string', + format: 'misskey:id', + nullable: true, + default: null + }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const foldersQuery = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) - .andWhere('folder.userId = :userId', { userId: user.id }); - const filesQuery = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) - .andWhere('file.userId = :userId', { userId: user.id }); - - if (ps.folderId) { - foldersQuery.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); - filesQuery.andWhere('file.parentId = :parentId', { parentId: ps.folderId }); - } else { - foldersQuery.andWhere('folder.parentId IS NULL'); - filesQuery.andWhere('file.parentId IS NULL'); + let orderBy = 'type ASC, id DESC'; + switch (ps.sort) { + case '+created': + orderBy = '"createdAt" DESC'; + break; + case '-created': + orderBy = '"createdAt" ASC'; + break; + case '+name': + orderBy = 'name DESC'; + break; + case '-name': + orderBy = 'name ASC'; + break; } - const folders = await foldersQuery.take(ps.limit).getMany(); + // due to the way AID is constructed, we can be sure that the IDs are not duplicated across tables. + const ids = await db.query( + 'SELECT id FROM (SELECT id, "userId", "parentId", "createdAt", name, 0 AS type FROM drive_folder' + + ' UNION SELECT id, "userId", "parentId", "createdAt", name, 1 AS type FROM drive_file) AS x' + + ' WHERE "userId" = $1 AND "parentId"' + + (ps.folderId ? '= $4' : 'IS NULL') + + ' ORDER BY ' + orderBy + + ' LIMIT $2 OFFSET $3', + [user.id, ps.limit, ps.offset, ...(ps.folderId ? [ps.folderId] : [])] + ).then(items => items.map(({ id }) => id)); - const [files, ...packedFolders] = await Promise.all([ - filesQuery.take(ps.limit - folders.length).getMany(), - ...(folders.map(folder => DriveFolders.pack(folder))), + const [folders, files] = await Promise.all([ + DriveFolders.findBy({ + id: In(ids), + }) + .then(folders => Promise.all(folders.map(folder => DriveFolders.pack(folder)))), + DriveFiles.findBy({ + id: In(ids), + }) + .then(files => DriveFiles.packMany(files, { detail: false, self: true })), ]); - const packedFiles = await DriveFiles.packMany(files, { detail: false, self: true }); + // merge folders/files into one array, keeping the original sorting + let merged = []; + for (const folder of folders) { + merged[ids.indexOf(folder.id)] = folder; + } + for (const file of files) { + merged[ids.indexOf(file.id)] = file; + } - return [ - ...packedFolders, - ...packedFiles, - ]; + return merged; }); From 7401a0b400c8014bfd20b732af3f9a63a0c458d7 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 10:09:38 +0200 Subject: [PATCH 22/93] client: fix drive pagination parameters --- packages/client/src/components/drive.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index a3b67a5b2..b82bd15f4 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -483,6 +483,7 @@ function goRoot() { const pagination = { endpoint: 'drive/show' as const, limit: 30, + offsetMode: true, params: computed(() => ({ folderId: folder?.id ?? null, })), From aac6a93bae892430fab3df01c27004d0e7a6c5c0 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 10:10:11 +0200 Subject: [PATCH 23/93] client: center drive loading ui --- packages/client/src/components/drive.vue | 63 ++++++++++++------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index b82bd15f4..a1d71a7ad 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -38,7 +38,6 @@
@@ -631,13 +632,13 @@ onBeforeUnmount(() => { height: calc(100% - 38px - 100px); } - > .contents { + .contents { display: grid; grid-template-columns: repeat(5, 1fr); gap: .5em; } - > .empty { + .empty { padding: 16px; text-align: center; pointer-events: none; From 134c3b43e65dfcae406f1fbc532c5fceb14a728c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 12:03:43 +0200 Subject: [PATCH 24/93] implement filtering and sorting in drive The `sort` parameter for /api/drive/show is now more unified with other endpoints which use +createdAt for sort instead of +created. closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/109 Changelog: Added --- .../src/server/api/endpoints/drive/show.ts | 21 +++++++++------ packages/client/src/components/drive.vue | 26 ++++++++++++++++++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/drive/show.ts b/packages/backend/src/server/api/endpoints/drive/show.ts index 54490be0a..4f27c28c9 100644 --- a/packages/backend/src/server/api/endpoints/drive/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/show.ts @@ -46,8 +46,8 @@ export const paramDef = { sort: { type: 'string', enum: [ - '+created', - '-created', + '+createdAt', + '-createdAt', '+name', '-name', ], @@ -58,6 +58,11 @@ export const paramDef = { nullable: true, default: null }, + name: { + description: 'Filters the output for files and folders that contain the given string (case insensitive).', + type: 'string', + default: '', + }, }, required: [], } as const; @@ -66,10 +71,10 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { let orderBy = 'type ASC, id DESC'; switch (ps.sort) { - case '+created': + case '+createdAt': orderBy = '"createdAt" DESC'; break; - case '-created': + case '-createdAt': orderBy = '"createdAt" ASC'; break; case '+name': @@ -84,11 +89,11 @@ export default define(meta, paramDef, async (ps, user) => { const ids = await db.query( 'SELECT id FROM (SELECT id, "userId", "parentId", "createdAt", name, 0 AS type FROM drive_folder' + ' UNION SELECT id, "userId", "parentId", "createdAt", name, 1 AS type FROM drive_file) AS x' - + ' WHERE "userId" = $1 AND "parentId"' - + (ps.folderId ? '= $4' : 'IS NULL') + + ' WHERE "userId" = $1 AND name ILIKE $2 AND "parentId"' + + (ps.folderId ? '= $5' : 'IS NULL') + ' ORDER BY ' + orderBy - + ' LIMIT $2 OFFSET $3', - [user.id, ps.limit, ps.offset, ...(ps.folderId ? [ps.folderId] : [])] + + ' LIMIT $3 OFFSET $4', + [user.id, '%' + ps.name + '%', ps.limit, ps.offset, ...(ps.folderId ? [ps.folderId] : [])] ).then(items => items.map(({ id }) => id)); const [folders, files] = await Promise.all([ diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index a1d71a7ad..eb7a6b2fc 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -35,6 +35,24 @@ @drop.prevent.stop="onDrop" @contextmenu.stop="onContextmenu" > + + + + + + + + + + + + + + + + + + (); let paginationElem = $ref>(); - let fileInput = $ref(); const uploadings = uploads; @@ -125,6 +145,8 @@ let folder = $ref(null); let hierarchyFolders = $ref([]); let selected = $ref>([]); let keepOriginal = $ref(defaultStore.state.keepOriginalUploading); +let searchName = $ref(''); +let sort = $ref(undefined); // ドロップされようとしているか let draghover = $ref(false); @@ -486,6 +508,8 @@ const pagination = { limit: 30, offsetMode: true, params: computed(() => ({ + sort, + name: searchName, folderId: folder?.id ?? null, })), }; From 6722ccfc64101be96fce43a3bb511889301da278 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 26 Mar 2023 13:40:26 +0200 Subject: [PATCH 25/93] client: remove separate empty drive/empty folder messages These messages are wrong when using the newly added filter and there are no filtering results. Instead of adding yet another separate message for an empty pagination, just show the default empty thingy the pagination component provides already. --- locales/en-US.yml | 2 -- packages/client/src/components/drive.vue | 5 ----- 2 files changed, 7 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index a7c7316bf..25158511b 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -278,8 +278,6 @@ createFolder: "Create a folder" renameFolder: "Rename this folder" deleteFolder: "Delete this folder" addFile: "Add a file" -emptyDrive: "Your Drive is empty" -emptyFolder: "This folder is empty" unableToDelete: "Unable to delete" inputNewFileName: "Enter a new filename" inputNewDescription: "Enter new caption" diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index eb7a6b2fc..1dff1e585 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -57,11 +57,6 @@ ref="paginationElem" :pagination="pagination" > - -