107 lines
4.4 KiB
TypeScript
107 lines
4.4 KiB
TypeScript
import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
|
import { UUID } from "https://deno.land/x/web_bson@v0.3.0/mod.js";
|
|
|
|
import { auth, body, headers, jwt, page, rest, schema } from "../server/decorators.ts";
|
|
import { JWTPayload } from "../utils/JWT.ts";
|
|
import AppRouter from "./AppRouter.ts";
|
|
import HttpError from "../server/HttpError.ts";
|
|
import User from "../models/User.ts";
|
|
import Image, { Visibility } from "../models/Image.ts";
|
|
import { Page } from "../server/decorators/page.ts";
|
|
import { Headers } from "../server/RestRequest.ts";
|
|
import { convert } from "../server/decorators/schema.ts";
|
|
import { now } from "../utils/utils.ts";
|
|
|
|
export default class ImageRouter extends AppRouter {
|
|
public static serialize(image: Image) {
|
|
return {
|
|
author: image.author,
|
|
created: image.created,
|
|
name: image.name,
|
|
visibility: image.visibility,
|
|
id: image._id,
|
|
};
|
|
}
|
|
|
|
@rest('GET', '/')
|
|
async get(@schema('uuid') id: UUID, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) {
|
|
const image = await this.images.findOne({ _id: new UUID(id) });
|
|
|
|
if (
|
|
!image ||
|
|
image.visibility === Visibility.Private && image.author !== jwt?.name
|
|
) throw new HttpError("Image doesn't exist.");
|
|
|
|
return ImageRouter.serialize(image);
|
|
}
|
|
|
|
@rest('GET', '/mine')
|
|
async mine(@page() page: Page, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
|
|
const user = await this.users.findOne({ username: jwt.name });
|
|
if (!user) throw new HttpError("You don't exist.");
|
|
const res = await page.apply(this.images.find({ _id: { $in: user.images } })).toArray();
|
|
return res.map(v => ImageRouter.serialize(v));
|
|
}
|
|
|
|
@rest('POST', '/upload')
|
|
async upload(@body() body: Blob, @headers() headers: Headers, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
|
|
const user = await this.users.findOne({ username: jwt.name });
|
|
if (!user) throw new HttpError("You don't exist.");
|
|
|
|
// Parse body
|
|
const contentType = headers['content-type'] + "";
|
|
if (!contentType.startsWith('multipart/form-data')) {
|
|
throw new HttpError("Expected a 'Content-type: multipart/form-data; ...' header.");
|
|
}
|
|
const data = await new Request('http://127.0.0.1', {
|
|
body, headers: [ ['content-type', contentType ] ],
|
|
method: 'post'
|
|
}).formData();
|
|
|
|
// Clean up data
|
|
if (!data.has('file')) throw new HttpError("Expected a 'file' entry in form data.");
|
|
if (!data.has('body')) throw new HttpError("Expected a 'body' entry in form data.");
|
|
|
|
let rawFile: File = data.get('file') as File;
|
|
let rawReq: string = data.get('body') as string;
|
|
|
|
if (typeof rawFile === 'string') rawFile = new File([rawFile], 'unknown');
|
|
if (typeof rawReq !== 'string') rawReq = await (rawReq as Blob).text();
|
|
if (rawFile.size > (1 << 20) * 2) throw new HttpError("File too large (max 2MB).");
|
|
|
|
// Extract (and check) extension
|
|
const pointI = rawFile.name.lastIndexOf(".");
|
|
if (pointI < 0) throw new HttpError("Given file has no extension.");
|
|
const ext = rawFile.name.substring(pointI + 1).trim();
|
|
if (ext === "") throw new HttpError("Given file has no extension.");
|
|
|
|
// Clean up request
|
|
const req = await convert(rawReq, { name: 'string?', visibility: 'number?' });
|
|
req.name ??= new UUID().toString();
|
|
req.visibility = 0;
|
|
if (req.visibility < 0 || req.visibility > 2) throw new HttpError("body.visibility: Must be 0, 1, or 2");
|
|
|
|
// Create file
|
|
const img: Image = { _id: new UUID(), author: user.username, created: now(), name: req.name!, visibility: req.visibility };
|
|
await Deno.mkdir('images', { recursive: true });
|
|
const out = await Deno.open(`images/${img._id}.${ext}`, { write: true, create: true });
|
|
|
|
for await (const bit of rawFile.stream()) out.write(bit);
|
|
out.close();
|
|
|
|
// Write to DB
|
|
try {
|
|
this.images.insertOne(img);
|
|
return ImageRouter.serialize(img);
|
|
}
|
|
catch (e) {
|
|
await Deno.remove(`images/${img._id}.${ext}`);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public constructor(private salt: string, private images: Collection<Image>, private users: Collection<User>) {
|
|
super();
|
|
users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] });
|
|
}
|
|
} |