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; });