cant remember

This commit is contained in:
TopchetoEU 2023-06-27 12:34:28 +03:00
parent 2b79aff2c7
commit b54241a495
No known key found for this signature in database
GPG Key ID: 24E57B2E9C61AD19
16 changed files with 147 additions and 70 deletions

View File

@ -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();

View File

@ -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[];
}

View File

@ -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' } ] });

View File

@ -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) {

View File

@ -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 {};

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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.");
}
});
}

View File

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

View File

@ -1,3 +0,0 @@
export default function now() {
return new Date().getTime() / 1000;
}

6
src/utils/utils.ts Normal file
View File

@ -0,0 +1,6 @@
export function now() {
return new Date().getTime() / 1000;
}
export function stream(...text: string[]) {
return new Blob(text).stream();
}