diff --git a/.gitignore b/.gitignore index 89bb5eb..41745ab 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ coverage /docker-compose.yml # misskey -# built +# Built elasticsearch redis meili_data @@ -45,7 +45,7 @@ api-docs.json *.log *.code-workspace .DS_Store -files +#files ormconfig.json packages/backend/assets/instance.css packages/backend/assets/sounds/None.mp3 diff --git a/packages/backend/built/server/api/endpoints/drive/files/attached-notes.js b/packages/backend/built/server/api/endpoints/drive/files/attached-notes.js new file mode 100644 index 0000000..af7f722 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/attached-notes.js @@ -0,0 +1,58 @@ +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFiles, Notes } from "../../../../../models/index.js"; +export const meta = { + tags: [ + "drive", + "notes" + ], + requireCredential: true, + kind: "read:drive", + description: "Find the notes to which the given file is attached.", + res: { + type: "array", + optional: false, + nullable: false, + items: { + type: "object", + optional: false, + nullable: false, + ref: "Note" + } + }, + errors: { + noSuchFile: { + message: "No such file.", + code: "NO_SUCH_FILE", + id: "c118ece3-2e4b-4296-99d1-51756e32d232" + } + } +}; +export const paramDef = { + type: "object", + properties: { + fileId: { + type: "string", + format: "misskey:id" + } + }, + required: [ + "fileId" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + // Fetch file + const file = await DriveFiles.findOneBy({ + id: ps.fileId, + userId: user.id + }); + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + const notes = await Notes.createQueryBuilder("note").where(":file = ANY(note.fileIds)", { + file: file.id + }).getMany(); + return await Notes.packMany(notes, user, { + detail: true + }); +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/caption-image.js b/packages/backend/built/server/api/endpoints/drive/files/caption-image.js new file mode 100644 index 0000000..01293c0 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/caption-image.js @@ -0,0 +1,34 @@ +import define from "../../../define.js"; +import { createWorker } from "tesseract.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "read:drive", + description: "Return caption of image", + res: { + type: "string", + optional: false, + nullable: false + } +}; +export const paramDef = { + type: "object", + properties: { + url: { + type: "string" + } + }, + required: [ + "url" + ] +}; +export default define(meta, paramDef, async (ps)=>{ + const worker = await createWorker("eng", undefined, { + logger: (m)=>console.log(m) + }); + const { data: { text } } = await worker.recognize(ps.url); + await worker.terminate(); + return text; +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/check-existence.js b/packages/backend/built/server/api/endpoints/drive/files/check-existence.js new file mode 100644 index 0000000..876cb0c --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/check-existence.js @@ -0,0 +1,35 @@ +import define from "../../../define.js"; +import { DriveFiles } from "../../../../../models/index.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "read:drive", + description: "Check if a given file exists.", + res: { + type: "boolean", + optional: false, + nullable: false + } +}; +export const paramDef = { + type: "object", + properties: { + md5: { + type: "string" + } + }, + required: [ + "md5" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + const exist = await DriveFiles.exist({ + where: { + md5: ps.md5, + userId: user.id + } + }); + return exist; +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/create.js b/packages/backend/built/server/api/endpoints/drive/files/create.js new file mode 100644 index 0000000..0b263d0 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/create.js @@ -0,0 +1,132 @@ +import { addFile } from "../../../../../services/drive/add-file.js"; +import { DriveFiles } from "../../../../../models/index.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "../../../../../misc/hard-limits.js"; +import { IdentifiableError } from "../../../../../misc/identifiable-error.js"; +import { fetchMeta } from "../../../../../misc/fetch-meta.js"; +import { HOUR } from "../../../../../const.js"; +import define from "../../../define.js"; +import { apiLogger } from "../../../logger.js"; +import { ApiError } from "../../../error.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + limit: { + duration: HOUR, + max: 120 + }, + requireFile: true, + kind: "write:drive", + description: "Upload a new drive file.", + res: { + type: "object", + optional: false, + nullable: false, + ref: "DriveFile" + }, + errors: { + invalidFileName: { + message: "Invalid file name.", + code: "INVALID_FILE_NAME", + id: "f449b209-0c60-4e51-84d5-29486263bfd4" + }, + inappropriate: { + message: "Cannot upload the file because it has been determined that it possibly contains inappropriate content.", + code: "INAPPROPRIATE", + id: "bec5bd69-fba3-43c9-b4fb-2894b66ad5d2" + }, + noFreeSpace: { + message: "Cannot upload the file because you have no free space of drive.", + code: "NO_FREE_SPACE", + id: "d08dbc37-a6a9-463a-8c47-96c32ab5f064" + }, + noFreeDatabaseSpace: { + message: "Cannot upload the database file because you have no free database space.", + code: "NO_FREE_DATABASE_SPACE", + id: "7b7d71d3-bcf4-4d61-94a2-cc6b11244129" + } + } +}; +export const paramDef = { + type: "object", + properties: { + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null + }, + name: { + type: "string", + nullable: true, + default: null + }, + comment: { + type: "string", + nullable: true, + maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, + default: null + }, + isSensitive: { + type: "boolean", + default: false + }, + isDatabase: { + type: "boolean", + default: false + }, + force: { + type: "boolean", + default: false + } + }, + required: [] +}; +export default define(meta, paramDef, async (ps, user, _, file, cleanup, ip, headers)=>{ + // Get 'name' parameter + let name = ps.name || file.originalname; + if (name !== undefined && name !== null) { + name = name.trim(); + if (name.length === 0) { + name = null; + } else if (name === "blob") { + name = null; + } else if (!DriveFiles.validateFileName(name)) { + throw new ApiError(meta.errors.invalidFileName); + } + } else { + name = null; + } + const meta = await fetchMeta(); + try { + // Create file + const driveFile = await addFile({ + user, + path: file.path, + name, + comment: ps.comment, + folderId: ps.folderId, + force: ps.force, + sensitive: ps.isSensitive, + isDatabase: ps.isDatabase, + requestIp: meta.enableIpLogging ? ip : null, + requestHeaders: meta.enableIpLogging ? headers : null + }); + return await DriveFiles.pack(driveFile, { + self: true + }); + } catch (e) { + if (e instanceof Error || typeof e === "string") { + apiLogger.error(e); + } + if (e instanceof IdentifiableError) { + if (e.id === "282f77bf-5816-4f72-9264-aa14d8261a21") throw new ApiError(meta.errors.inappropriate); + if (e.id === "c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6") throw new ApiError(meta.errors.noFreeSpace); + if (e.id === "9ec2c4e9-c4df-4f40-b80b-4f57b32d28df") throw new ApiError(meta.errors.noFreeDatabaseSpace); + } + throw new ApiError(); + } finally{ + cleanup(); + } +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/delete.js b/packages/backend/built/server/api/endpoints/drive/files/delete.js new file mode 100644 index 0000000..fc1e0e1 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/delete.js @@ -0,0 +1,52 @@ +import { deleteFile } from "../../../../../services/drive/delete-file.js"; +import { publishDriveStream } from "../../../../../services/stream.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFiles } from "../../../../../models/index.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "write:drive", + description: "Delete an existing drive file.", + errors: { + noSuchFile: { + message: "No such file.", + code: "NO_SUCH_FILE", + id: "908939ec-e52b-4458-b395-1025195cea58" + }, + accessDenied: { + message: "Access denied.", + code: "ACCESS_DENIED", + id: "5eb8d909-2540-4970-90b8-dd6f86088121" + } + } +}; +export const paramDef = { + type: "object", + properties: { + fileId: { + type: "string", + format: "misskey:id" + } + }, + required: [ + "fileId" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + const file = await DriveFiles.findOneBy({ + id: ps.fileId + }); + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + if (!(user.isAdmin || user.isModerator) && file.userId !== user.id) { + throw new ApiError(meta.errors.accessDenied); + } + // Delete + await deleteFile(file); + // Publish fileDeleted event + publishDriveStream(user.id, "fileDeleted", file.id); +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/find-by-hash.js b/packages/backend/built/server/api/endpoints/drive/files/find-by-hash.js new file mode 100644 index 0000000..9ee0678 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/find-by-hash.js @@ -0,0 +1,41 @@ +import { DriveFiles } from "../../../../../models/index.js"; +import define from "../../../define.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "read:drive", + description: "Search for a drive file by a hash of the contents.", + res: { + type: "array", + optional: false, + nullable: false, + items: { + type: "object", + optional: false, + nullable: false, + ref: "DriveFile" + } + } +}; +export const paramDef = { + type: "object", + properties: { + md5: { + type: "string" + } + }, + required: [ + "md5" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + const files = await DriveFiles.findBy({ + md5: ps.md5, + userId: user.id + }); + return await DriveFiles.packMany(files, { + self: true + }); +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/find.js b/packages/backend/built/server/api/endpoints/drive/files/find.js new file mode 100644 index 0000000..b2ca441 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/find.js @@ -0,0 +1,49 @@ +import define from "../../../define.js"; +import { DriveFiles } from "../../../../../models/index.js"; +import { IsNull } from "typeorm"; +export const meta = { + requireCredential: true, + tags: [ + "drive" + ], + kind: "read:drive", + description: "Search for a drive file by the given parameters.", + res: { + type: "array", + optional: false, + nullable: false, + items: { + type: "object", + optional: false, + nullable: false, + ref: "DriveFile" + } + } +}; +export const paramDef = { + type: "object", + properties: { + name: { + type: "string" + }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null + } + }, + required: [ + "name" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + const files = await DriveFiles.findBy({ + name: ps.name, + userId: user.id, + folderId: ps.folderId ?? IsNull() + }); + return await Promise.all(files.map((file)=>DriveFiles.pack(file, { + self: true + }))); +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/show.js b/packages/backend/built/server/api/endpoints/drive/files/show.js new file mode 100644 index 0000000..262d6d5 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/show.js @@ -0,0 +1,88 @@ +import { DriveFiles } from "../../../../../models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "read:drive", + description: "Show the properties of a drive file.", + res: { + type: "object", + optional: false, + nullable: false, + ref: "DriveFile" + }, + errors: { + noSuchFile: { + message: "No such file.", + code: "NO_SUCH_FILE", + id: "067bc436-2718-4795-b0fb-ecbe43949e31" + }, + accessDenied: { + message: "Access denied.", + code: "ACCESS_DENIED", + id: "25b73c73-68b1-41d0-bad1-381cfdf6579f" + } + } +}; +export const paramDef = { + type: "object", + anyOf: [ + { + properties: { + fileId: { + type: "string", + format: "misskey:id" + } + }, + required: [ + "fileId" + ] + }, + { + properties: { + url: { + type: "string" + } + }, + required: [ + "url" + ] + } + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + let file = null; + if (ps.fileId) { + file = await DriveFiles.findOneBy({ + id: ps.fileId + }); + } else if (ps.url) { + file = await DriveFiles.findOne({ + where: [ + { + url: ps.url + }, + { + webpublicUrl: ps.url + }, + { + thumbnailUrl: ps.url + } + ] + }); + } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + if (!(user.isAdmin || user.isModerator) && file.userId !== user.id) { + throw new ApiError(meta.errors.accessDenied); + } + return await DriveFiles.pack(file, { + detail: true, + withUser: true, + self: true + }); +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/update.js b/packages/backend/built/server/api/endpoints/drive/files/update.js new file mode 100644 index 0000000..4bfd1c5 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/update.js @@ -0,0 +1,117 @@ +import { publishDriveStream } from "../../../../../services/stream.js"; +import { DriveFiles, DriveFolders } from "../../../../../models/index.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "../../../../../misc/hard-limits.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +export const meta = { + tags: [ + "drive" + ], + requireCredential: true, + kind: "write:drive", + description: "Update the properties of a drive file.", + errors: { + invalidFileName: { + message: "Invalid file name.", + code: "INVALID_FILE_NAME", + id: "395e7156-f9f0-475e-af89-53c3c23080c2" + }, + noSuchFile: { + message: "No such file.", + code: "NO_SUCH_FILE", + id: "e7778c7e-3af9-49cd-9690-6dbc3e6c972d" + }, + accessDenied: { + message: "Access denied.", + code: "ACCESS_DENIED", + id: "01a53b27-82fc-445b-a0c1-b558465a8ed2" + }, + noSuchFolder: { + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "ea8fb7a5-af77-4a08-b608-c0218176cd73" + } + }, + res: { + type: "object", + optional: false, + nullable: false, + ref: "DriveFile" + } +}; +export const paramDef = { + type: "object", + properties: { + fileId: { + type: "string", + format: "misskey:id" + }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true + }, + name: { + type: "string" + }, + isSensitive: { + type: "boolean" + }, + allowDownload: { + type: "boolean" + }, + comment: { + type: "string", + nullable: true, + maxLength: DB_MAX_IMAGE_COMMENT_LENGTH + } + }, + required: [ + "fileId" + ] +}; +export default define(meta, paramDef, async (ps, user)=>{ + const file = await DriveFiles.findOneBy({ + id: ps.fileId + }); + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + if (!(user.isAdmin || user.isModerator) && file.userId !== user.id) { + throw new ApiError(meta.errors.accessDenied); + } + if (ps.name) file.name = ps.name; + if (!DriveFiles.validateFileName(file.name)) { + throw new ApiError(meta.errors.invalidFileName); + } + if (ps.comment !== undefined) file.comment = ps.comment; + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; + if (ps.allowDownload !== undefined) file.allowDownload = ps.allowDownload; + if (ps.folderId !== undefined) { + if (ps.folderId === null) { + file.folderId = null; + } else { + const folder = await DriveFolders.findOneBy({ + id: ps.folderId, + userId: user.id + }); + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } + file.folderId = folder.id; + } + } + await DriveFiles.update(file.id, { + name: file.name, + comment: file.comment, + folderId: file.folderId, + isSensitive: file.isSensitive, + allowDownload: file.allowDownload + }); + const fileObj = await DriveFiles.pack(file, { + self: true + }); + // Publish fileUpdated event + publishDriveStream(user.id, "fileUpdated", fileObj); + return fileObj; +}); diff --git a/packages/backend/built/server/api/endpoints/drive/files/upload-from-url.js b/packages/backend/built/server/api/endpoints/drive/files/upload-from-url.js new file mode 100644 index 0000000..bf89070 --- /dev/null +++ b/packages/backend/built/server/api/endpoints/drive/files/upload-from-url.js @@ -0,0 +1,72 @@ +import { uploadFromUrl } from "../../../../../services/drive/upload-from-url.js"; +import define from "../../../define.js"; +import { DriveFiles } from "../../../../../models/index.js"; +import { publishMainStream } from "../../../../../services/stream.js"; +import { HOUR } from "../../../../../const.js"; +export const meta = { + tags: [ + "drive" + ], + limit: { + duration: HOUR, + max: 60 + }, + description: "Request the server to download a new drive file from the specified URL.", + requireCredential: true, + kind: "write:drive" +}; +export const paramDef = { + type: "object", + properties: { + url: { + type: "string" + }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null + }, + isSensitive: { + type: "boolean", + default: false + }, + comment: { + type: "string", + nullable: true, + maxLength: 512, + default: null + }, + marker: { + type: "string", + nullable: true, + default: null + }, + force: { + type: "boolean", + default: false + } + }, + required: [ + "url" + ] +}; +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)=>{ + DriveFiles.pack(file, { + self: true + }).then((packedFile)=>{ + publishMainStream(user.id, "urlUploadFinished", { + marker: ps.marker, + file: packedFile + }); + }); + }); +});