cant remember
This commit is contained in:
parent
2b79aff2c7
commit
b54241a495
@ -17,7 +17,7 @@ export default async function clonegur() {
|
|||||||
db: 'clonegur',
|
db: 'clonegur',
|
||||||
servers: [ { host: '127.0.0.1', port: 27017 } ]
|
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();
|
await clonegur();
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
||||||
|
|
||||||
export default interface User {
|
export default interface User {
|
||||||
_id: string;
|
_id: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
images: UUID[];
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts";
|
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 { 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 { JWTPayload } from "../utils/JWT.ts";
|
||||||
import AppRouter from "./AppRouter.ts";
|
import AppRouter from "./AppRouter.ts";
|
||||||
import HttpError from "../server/HttpError.ts";
|
import HttpError from "../server/HttpError.ts";
|
||||||
import User from "../models/User.ts";
|
import User from "../models/User.ts";
|
||||||
import Image, { Visibility } from "../models/Image.ts";
|
import Image, { Visibility } from "../models/Image.ts";
|
||||||
|
import { Page } from "../server/decorators/page.ts";
|
||||||
|
|
||||||
export default class ImageRouter extends AppRouter {
|
export default class ImageRouter extends AppRouter {
|
||||||
public static serialize(image: Image) {
|
public static serialize(image: Image) {
|
||||||
@ -20,7 +21,7 @@ export default class ImageRouter extends AppRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@rest('GET', '/')
|
@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) });
|
const image = await this.images.findOne({ _id: new UUID(id) });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -31,6 +32,14 @@ export default class ImageRouter extends AppRouter {
|
|||||||
return ImageRouter.serialize(image);
|
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>) {
|
public constructor(private salt: string, private images: Collection<Image>, private users: Collection<User>) {
|
||||||
super();
|
super();
|
||||||
users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] });
|
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 { rest, route } from "../server/decorators.ts";
|
||||||
import User from "../models/User.ts";
|
import User from "../models/User.ts";
|
||||||
import Image from "../models/Image.ts";
|
import Image from "../models/Image.ts";
|
||||||
|
import { stream } from "../utils/utils.ts";
|
||||||
|
|
||||||
export class RootRouter extends AppRouter {
|
export class RootRouter extends AppRouter {
|
||||||
@route('users/*') users;
|
@route('users/*') users;
|
||||||
@ -13,7 +14,7 @@ export class RootRouter extends AppRouter {
|
|||||||
|
|
||||||
@rest('*', '*')
|
@rest('*', '*')
|
||||||
default() {
|
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) {
|
constructor(salt: string, db: Database) {
|
||||||
|
@ -7,7 +7,7 @@ import User from "../models/User.ts";
|
|||||||
import AppRouter from "./AppRouter.ts";
|
import AppRouter from "./AppRouter.ts";
|
||||||
import jwt from "../server/decorators/jwt.ts";
|
import jwt from "../server/decorators/jwt.ts";
|
||||||
import JWT, { JWTPayload } from "../utils/JWT.ts";
|
import JWT, { JWTPayload } from "../utils/JWT.ts";
|
||||||
import now from "../utils/now.ts";
|
import { now } from "../utils/utils.ts";
|
||||||
|
|
||||||
export interface SignupRequest {
|
export interface SignupRequest {
|
||||||
username: string;
|
username: string;
|
||||||
@ -53,6 +53,7 @@ export default class UserRouter extends AppRouter {
|
|||||||
await this.users.insertOne({
|
await this.users.insertOne({
|
||||||
username: body.username,
|
username: body.username,
|
||||||
password: password,
|
password: password,
|
||||||
|
images: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
return {};
|
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 raw = msg.request.body;
|
||||||
const headers = {} as Headers;
|
const headers = {} as Headers;
|
||||||
|
|
||||||
@ -103,18 +103,10 @@ export default class RestRequest {
|
|||||||
headers[entry[0]] = entry[1];
|
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 url = new URL(msg.request.url);
|
||||||
const params = {} as ParamDict;
|
const params = {} as ParamDict;
|
||||||
for (const entry of url.searchParams.entries()) params[entry[0]] = entry[1];
|
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";
|
import { Headers } from "./RestRequest.ts";
|
||||||
|
|
||||||
export default class RestResponse {
|
export default class RestResponse {
|
||||||
#status = 200;
|
#status = 200;
|
||||||
#statusMsg = '';
|
#statusMsg = '';
|
||||||
#body?: Blob;
|
#body?: ReadableStream<Uint8Array>;
|
||||||
|
|
||||||
headers: Headers = {};
|
headers: Headers = {};
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ export default class RestResponse {
|
|||||||
this.#statusMsg = message;
|
this.#statusMsg = message;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public body(val: string | Blob) {
|
public body(val: string | ReadableStream<Uint8Array>) {
|
||||||
if (typeof val === 'string') val = new Blob([val]);
|
if (typeof val === 'string') val = stream(val);
|
||||||
this.#body = val;
|
this.#body = val;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ export default class Router {
|
|||||||
public async attach(server: Deno.Listener) {
|
public async attach(server: Deno.Listener) {
|
||||||
for await (const conn of server) {
|
for await (const conn of server) {
|
||||||
for await (const req of Deno.serveHttp(conn)) {
|
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());
|
if (r) req.respondWith(r.toFetchResponse());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,6 @@ import auth from "./decorators/auth.ts";
|
|||||||
import route from "./decorators/route.ts";
|
import route from "./decorators/route.ts";
|
||||||
import schema from "./decorators/schema.ts";
|
import schema from "./decorators/schema.ts";
|
||||||
import jwt from "./decorators/jwt.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";
|
import { makeParameterModifier } from "../Router.ts";
|
||||||
|
|
||||||
export type BodyType = 'raw' | 'json';
|
export type BodyType = 'raw' | 'json';
|
||||||
|
|
||||||
export default function body(type: BodyType = 'json') {
|
export default function body() {
|
||||||
return makeParameterModifier(async req => {
|
return makeParameterModifier(req => {
|
||||||
let body = req.body;
|
return 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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
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
|
// 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 HttpError from "../HttpError.ts";
|
||||||
import { makeParameterModifier } from "../Router.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 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) {
|
export default function schema(desc: Schema) {
|
||||||
function stringify(desc: Schema): string {
|
function stringify(desc: Schema): string {
|
||||||
@ -24,38 +25,86 @@ export default function schema(desc: Schema) {
|
|||||||
|
|
||||||
return res;
|
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('.');
|
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') {
|
if (typeof desc === 'string') {
|
||||||
let type: string = desc;
|
let type: string = desc;
|
||||||
const opt = desc.endsWith('?');
|
const opt = desc.endsWith('?');
|
||||||
|
|
||||||
if (opt) type = type.substring(0, desc.length - 1);
|
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) {
|
else if (desc instanceof Array) {
|
||||||
for (const type of desc) {
|
for (const type of desc) {
|
||||||
try {
|
try {
|
||||||
test(path, val, type);
|
return convert(path, val, type);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
catch { /**/ }
|
catch { /**/ }
|
||||||
}
|
}
|
||||||
throw new HttpError(`${_path}: Expected a ${stringify(desc)}, got ${typeof val} instead.`);
|
throw new HttpError(`${_path}: Expected a ${stringify(desc)}, got ${typeof val} instead.`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (desc.$optional && val === undefined) return;
|
if (desc.$optional && val === undefined) return val;
|
||||||
if (typeof val !== 'object' || val === null) throw new HttpError(`${_path}: Expected an object, got ${typeof val} instead.`);
|
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) {
|
for (const key in desc) {
|
||||||
if (key === '$optional') continue;
|
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 undefinedBuff = new Blob([new TextEncoder().encode('undefined')]);
|
||||||
const nullBuff = new Blob([new TextEncoder().encode('null')]);
|
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) {
|
while (true) {
|
||||||
if (depth <= 0) throw new Error("Call depth exceeded limit.");
|
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--;
|
depth--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val === undefined) return undefinedBuff;
|
if (val === undefined) return undefinedBuff.stream();
|
||||||
if (val === null) return nullBuff;
|
if (val === null) return nullBuff.stream();
|
||||||
if (typeof val === 'string') return new Blob([val]);
|
if (typeof val === 'string') return stream(val);
|
||||||
if (val instanceof Blob) return 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) {
|
if (val.toString !== Object.prototype.toString && val.toString instanceof Function) {
|
||||||
val = val.toString();
|
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