cant remember
This commit is contained in:
parent
2b79aff2c7
commit
b54241a495
@ -17,7 +17,7 @@ export default async function clonegur() {
|
||||
db: 'clonegur',
|
||||
servers: [ { host: '127.0.0.1', port: 27017 } ]
|
||||
});
|
||||
await new RootRouter(salt, db.collection('users')).attach(Deno.listen({ port: 4000, hostname: 'localhost' }));
|
||||
await new RootRouter(salt, db).attach(Deno.listen({ port: 4000, hostname: 'localhost' }));
|
||||
}
|
||||
|
||||
await clonegur();
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
||||
|
||||
export default interface User {
|
||||
_id: string;
|
||||
username: string;
|
||||
password: string;
|
||||
images: UUID[];
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
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, jwt, rest, schema, uuid } from "../server/decorators.ts";
|
||||
import { auth, body, 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";
|
||||
|
||||
export default class ImageRouter extends AppRouter {
|
||||
public static serialize(image: Image) {
|
||||
@ -20,7 +21,7 @@ export default class ImageRouter extends AppRouter {
|
||||
}
|
||||
|
||||
@rest('GET', '/')
|
||||
async get(@uuid() @schema('string') id: string, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) {
|
||||
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 (
|
||||
@ -31,6 +32,14 @@ export default class ImageRouter extends AppRouter {
|
||||
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));
|
||||
}
|
||||
|
||||
public constructor(private salt: string, private images: Collection<Image>, private users: Collection<User>) {
|
||||
super();
|
||||
users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] });
|
||||
|
@ -6,6 +6,7 @@ import RestResponse from "../server/RestResponse.ts";
|
||||
import { rest, route } from "../server/decorators.ts";
|
||||
import User from "../models/User.ts";
|
||||
import Image from "../models/Image.ts";
|
||||
import { stream } from "../utils/utils.ts";
|
||||
|
||||
export class RootRouter extends AppRouter {
|
||||
@route('users/*') users;
|
||||
@ -13,7 +14,7 @@ export class RootRouter extends AppRouter {
|
||||
|
||||
@rest('*', '*')
|
||||
default() {
|
||||
return new RestResponse().body(new Blob(['Page not found :/'])).status(404);
|
||||
return new RestResponse().body(stream('Page not found :/')).status(404);
|
||||
}
|
||||
|
||||
constructor(salt: string, db: Database) {
|
||||
|
@ -7,7 +7,7 @@ import User from "../models/User.ts";
|
||||
import AppRouter from "./AppRouter.ts";
|
||||
import jwt from "../server/decorators/jwt.ts";
|
||||
import JWT, { JWTPayload } from "../utils/JWT.ts";
|
||||
import now from "../utils/now.ts";
|
||||
import { now } from "../utils/utils.ts";
|
||||
|
||||
export interface SignupRequest {
|
||||
username: string;
|
||||
@ -53,6 +53,7 @@ export default class UserRouter extends AppRouter {
|
||||
await this.users.insertOne({
|
||||
username: body.username,
|
||||
password: password,
|
||||
images: [],
|
||||
});
|
||||
|
||||
return {};
|
||||
|
@ -95,7 +95,7 @@ export default class RestRequest {
|
||||
);
|
||||
}
|
||||
|
||||
public static async fromMessage(msg: Deno.RequestEvent) {
|
||||
public static fromMessage(msg: Deno.RequestEvent) {
|
||||
const raw = msg.request.body;
|
||||
const headers = {} as Headers;
|
||||
|
||||
@ -103,18 +103,10 @@ export default class RestRequest {
|
||||
headers[entry[0]] = entry[1];
|
||||
}
|
||||
|
||||
const parts: Uint8Array[] = [];
|
||||
|
||||
if (raw !== null) {
|
||||
for await (const part of raw) {
|
||||
parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(msg.request.url);
|
||||
const params = {} as ParamDict;
|
||||
for (const entry of url.searchParams.entries()) params[entry[0]] = entry[1];
|
||||
|
||||
return new RestRequest(new Blob(parts), headers, msg.request.method, url.pathname, {}, params);
|
||||
return new RestRequest(raw, headers, msg.request.method, url.pathname, {}, params);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { stream } from "../utils/utils.ts";
|
||||
import { Headers } from "./RestRequest.ts";
|
||||
|
||||
export default class RestResponse {
|
||||
#status = 200;
|
||||
#statusMsg = '';
|
||||
#body?: Blob;
|
||||
#body?: ReadableStream<Uint8Array>;
|
||||
|
||||
headers: Headers = {};
|
||||
|
||||
@ -22,8 +23,8 @@ export default class RestResponse {
|
||||
this.#statusMsg = message;
|
||||
return this;
|
||||
}
|
||||
public body(val: string | Blob) {
|
||||
if (typeof val === 'string') val = new Blob([val]);
|
||||
public body(val: string | ReadableStream<Uint8Array>) {
|
||||
if (typeof val === 'string') val = stream(val);
|
||||
this.#body = val;
|
||||
return this;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export default class Router {
|
||||
public async attach(server: Deno.Listener) {
|
||||
for await (const conn of server) {
|
||||
for await (const req of Deno.serveHttp(conn)) {
|
||||
const r = await this.handle(await RestRequest.fromMessage(req));
|
||||
const r = await this.handle(RestRequest.fromMessage(req));
|
||||
if (r) req.respondWith(r.toFetchResponse());
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ import auth from "./decorators/auth.ts";
|
||||
import route from "./decorators/route.ts";
|
||||
import schema from "./decorators/schema.ts";
|
||||
import jwt from "./decorators/jwt.ts";
|
||||
import uuid from "./decorators/uuid.ts";
|
||||
import page from "./decorators/page.ts";
|
||||
|
||||
export { body, schema, rest, route, auth , jwt, uuid };
|
||||
export { body, schema, rest, route, auth , jwt, page };
|
@ -1,20 +1,9 @@
|
||||
import HttpError from "../HttpError.ts";
|
||||
import { makeParameterModifier } from "../Router.ts";
|
||||
|
||||
export type BodyType = 'raw' | 'json';
|
||||
|
||||
export default function body(type: BodyType = 'json') {
|
||||
return makeParameterModifier(async req => {
|
||||
let body = req.body;
|
||||
if (type === 'json') {
|
||||
try {
|
||||
if (body instanceof Blob) body = await body.text();
|
||||
if (typeof body === 'string') body = JSON.parse(body.toString());
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof SyntaxError) throw new HttpError('Body syntax error: ' + e.message);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
export default function body() {
|
||||
return makeParameterModifier(req => {
|
||||
return req.body;
|
||||
});
|
||||
}
|
39
src/server/decorators/page.ts
Normal file
39
src/server/decorators/page.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { makeParameterModifier } from "../Router.ts";
|
||||
import { FindCursor } from "https://deno.land/x/mongo@v0.31.2/src/collection/commands/find.ts";
|
||||
|
||||
export class Page {
|
||||
public size?: number;
|
||||
public index?: number;
|
||||
|
||||
public apply<T>(cursor: FindCursor<T>) {
|
||||
let res = cursor;
|
||||
|
||||
if (this.size !== undefined) {
|
||||
if (this.index !== undefined) res = res.skip(this.index * this.size);
|
||||
res = res.limit(this.size);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public constructor(size?: number);
|
||||
public constructor(size: number, index?: number);
|
||||
public constructor(size?: number, index?: number) {
|
||||
this.size = size;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
export default function uuid() {
|
||||
return makeParameterModifier(req => {
|
||||
let n: number | undefined = Number.parseInt(req.params.n);
|
||||
let i: number | undefined = Number.parseInt(req.params.i);
|
||||
|
||||
if (isNaN(n) || n < 1) n = undefined;
|
||||
if (isNaN(i) || i < 0) i = undefined;
|
||||
|
||||
if (n === undefined) return new Page();
|
||||
else if (i === undefined) return new Page(n);
|
||||
else return new Page(n, i);
|
||||
});
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
||||
import HttpError from "../HttpError.ts";
|
||||
import { makeParameterModifier } from "../Router.ts";
|
||||
|
||||
export type PrimitiveSchema = 'string' | 'number' | 'boolean' | 'object';
|
||||
export type PrimitiveSchema = 'string' | 'number' | 'boolean' | 'uuid' | 'object';
|
||||
export type OptionalSchema = `${PrimitiveSchema}?`;
|
||||
export type Schema = 'any' | PrimitiveSchema | OptionalSchema | Schema[] | ({ $optional?: boolean; } & { [key: string]: Schema });
|
||||
export type Schema = 'any' | 'null' | PrimitiveSchema | OptionalSchema | Schema[] | ({ $optional?: boolean; } & { [key: string]: Schema });
|
||||
|
||||
export default function schema(desc: Schema) {
|
||||
function stringify(desc: Schema): string {
|
||||
@ -24,38 +25,86 @@ export default function schema(desc: Schema) {
|
||||
|
||||
return res;
|
||||
}
|
||||
function test(path: string[], val: unknown, desc: Schema) {
|
||||
async function convert(path: string[], val: unknown, desc: Schema): Promise<any> {
|
||||
const _path = path.join('.');
|
||||
if (desc === 'any') return;
|
||||
if (val instanceof Blob) val = await val.text();
|
||||
if (val instanceof ReadableStream) {
|
||||
const res: Uint8Array[] = [];
|
||||
|
||||
for await (const part of val) {
|
||||
if (!(part instanceof Uint8Array)) throw new Error(`${_path}: Invalid stream given.`);
|
||||
res.push(part);
|
||||
}
|
||||
|
||||
val = await new Blob(res).text();
|
||||
}
|
||||
if (desc === 'any') return val;
|
||||
|
||||
if (typeof desc === 'string') {
|
||||
let type: string = desc;
|
||||
const opt = desc.endsWith('?');
|
||||
|
||||
if (opt) type = type.substring(0, desc.length - 1);
|
||||
if (opt && val === undefined) return;
|
||||
if (opt && val === undefined) return val;
|
||||
|
||||
if (typeof val as string !== type) throw new HttpError(`${_path}: Expected a ${type}, got ${typeof val} instead.`);
|
||||
switch (type) {
|
||||
case "null":
|
||||
if (val === null) return null;
|
||||
throw new HttpError(`${_path}: Expected null.`);
|
||||
case "string": return val + "";
|
||||
case "uuid":
|
||||
try {
|
||||
return new UUID(val + "");
|
||||
}
|
||||
catch {
|
||||
throw new HttpError(`${_path}: Expected an uuid or a value, convertible to an uuid.`);
|
||||
}
|
||||
case "number": {
|
||||
const res = Number.parseFloat(val + "");
|
||||
if (isNaN(res)) throw new HttpError(`${_path}: Expected a number or a value, convertible to a number.`);
|
||||
return res;
|
||||
}
|
||||
case "boolean": {
|
||||
const res = val + "";
|
||||
if (res === 'true') return true;
|
||||
if (res === 'false') return true;
|
||||
throw new HttpError(`${_path}: Expected a boolean or a value, convertible to a boolean.`);
|
||||
}
|
||||
default:
|
||||
throw new Error(`${_path}: Unknown type ${type}`);
|
||||
}
|
||||
}
|
||||
else if (desc instanceof Array) {
|
||||
for (const type of desc) {
|
||||
try {
|
||||
test(path, val, type);
|
||||
return;
|
||||
return convert(path, val, type);
|
||||
}
|
||||
catch { /**/ }
|
||||
}
|
||||
throw new HttpError(`${_path}: Expected a ${stringify(desc)}, got ${typeof val} instead.`);
|
||||
}
|
||||
else {
|
||||
if (desc.$optional && val === undefined) return;
|
||||
if (typeof val !== 'object' || val === null) throw new HttpError(`${_path}: Expected an object, got ${typeof val} instead.`);
|
||||
if (desc.$optional && val === undefined) return val;
|
||||
if (val === null) throw new HttpError(`${_path}: Expected an object, got null instead.`);
|
||||
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
val = JSON.parse(val);
|
||||
}
|
||||
catch (e) {
|
||||
if (val === null) throw new HttpError(`${_path}: Invalid JSON given for object: ${e}`);
|
||||
}
|
||||
}
|
||||
else throw new HttpError(`${_path}: Expected an object or a valid json string.`);
|
||||
|
||||
for (const key in desc) {
|
||||
if (key === '$optional') continue;
|
||||
test([ ...path, key ], (val as any)[key], desc[key]);
|
||||
(val as any)[key] = convert([ ...path, key ], (val as any)[key], desc[key]);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return makeParameterModifier((_req, val, name) => (test([name], val, desc), val));
|
||||
return makeParameterModifier(async (_req, val, name) => await convert([name], val, desc));
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
||||
import { makeParameterModifier } from "../Router.ts";
|
||||
import HttpError from "../HttpError.ts";
|
||||
|
||||
export default function uuid() {
|
||||
return makeParameterModifier((_req, val) => {
|
||||
try {
|
||||
return new UUID(val);
|
||||
}
|
||||
catch {
|
||||
throw new HttpError("Invalid UUID given.");
|
||||
}
|
||||
});
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { stream } from "../utils/utils.ts";
|
||||
|
||||
const undefinedBuff = new Blob([new TextEncoder().encode('undefined')]);
|
||||
const nullBuff = new Blob([new TextEncoder().encode('null')]);
|
||||
|
||||
export default async function serialize(val: unknown, depth = 16): Promise<Blob> {
|
||||
export default async function serialize(val: unknown, depth = 16): Promise<ReadableStream<Uint8Array>> {
|
||||
while (true) {
|
||||
if (depth <= 0) throw new Error("Call depth exceeded limit.");
|
||||
|
||||
@ -15,14 +17,16 @@ export default async function serialize(val: unknown, depth = 16): Promise<Blob>
|
||||
depth--;
|
||||
}
|
||||
|
||||
if (val === undefined) return undefinedBuff;
|
||||
if (val === null) return nullBuff;
|
||||
if (typeof val === 'string') return new Blob([val]);
|
||||
if (val instanceof Blob) return val;
|
||||
if (val === undefined) return undefinedBuff.stream();
|
||||
if (val === null) return nullBuff.stream();
|
||||
if (typeof val === 'string') return stream(val);
|
||||
if (val instanceof Blob) return val.stream();
|
||||
if (val instanceof ReadableStream) return val;
|
||||
if (val instanceof Uint8Array) return new Blob([val]).stream();
|
||||
|
||||
if (val.toString !== Object.prototype.toString && val.toString instanceof Function) {
|
||||
val = val.toString();
|
||||
}
|
||||
|
||||
return new Blob([JSON.stringify(val)]);
|
||||
return stream(JSON.stringify(val));
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function now() {
|
||||
return new Date().getTime() / 1000;
|
||||
}
|
6
src/utils/utils.ts
Normal file
6
src/utils/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function now() {
|
||||
return new Date().getTime() / 1000;
|
||||
}
|
||||
export function stream(...text: string[]) {
|
||||
return new Blob(text).stream();
|
||||
}
|
Loading…
Reference in New Issue
Block a user