mirror of
https://github.com/autismjs/monorepo.git
synced 2026-01-09 12:47:58 -05:00
basic api
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import IHttpServer, {
|
||||
HttpServerOptions,
|
||||
StatusCode,
|
||||
parseJsonBody,
|
||||
} from './services/web/index.ts';
|
||||
import IDatabase, { DatabaseOptions } from './services/db/index.ts';
|
||||
|
||||
@@ -30,24 +30,36 @@ export default class GroupRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
this.#http!.on('/health', (_, res) => {
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'text' });
|
||||
res.end('ok');
|
||||
this.#http!.get('/health', (_, res) => {
|
||||
res.send('ok');
|
||||
});
|
||||
|
||||
this.#http!.on('/groups/:id', async (req, res) => {
|
||||
this.#http!.get('/groups/:id/members', async (req, res) => {
|
||||
res.send(await this.#db?.getGroupMembers(req.params!.id));
|
||||
});
|
||||
|
||||
this.#http!.get('/groups/:id', async (req, res) => {
|
||||
const { id } = req.params!;
|
||||
const result = await this.#db?.getGroupInfo(id);
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(result));
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
this.#http!.on('/groups', async (req, res) => {
|
||||
console.time('groups');
|
||||
this.#http!.get('/groups', async (req, res) => {
|
||||
const result = await this.#db?.getGroups();
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(result));
|
||||
console.timeEnd('groups');
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
this.#http!.get('/commitments/:commitment', async (req, res) => {
|
||||
const { commitment } = req.params!;
|
||||
const result = await this.#db?.getGroupsByCommitment(commitment);
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
this.#http!.post('/groups/:id/members', parseJsonBody, async (req, res) => {
|
||||
const { id } = req.params!;
|
||||
const { commitment } = req.body!;
|
||||
await this.#db!.insertCommitment(commitment, id);
|
||||
res.send('ok');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,22 @@ export default class Database {
|
||||
|
||||
async #prepareTables() {
|
||||
await this.#pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
commitment VARCHAR(66) PRIMARY KEY,
|
||||
group_id VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS users_group_idx ON users (group_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
group_id VARCHAR(255) PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description VARCHAR(4095) NOT NULL,
|
||||
icon_url VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS commitments (
|
||||
commitment VARCHAR(66) NOT NULL,
|
||||
group_id VARCHAR(255) REFERENCES groups(group_id),
|
||||
UNIQUE(commitment, group_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS commitments_gp_idx ON commitments (group_id);
|
||||
CREATE INDEX IF NOT EXISTS commitments_cmt_idx ON commitments (commitment);
|
||||
|
||||
`);
|
||||
|
||||
for (const group of groups) {
|
||||
@@ -65,7 +68,7 @@ export default class Database {
|
||||
async insertCommitment(commitment: string, groupId: string) {
|
||||
await this.#pool.query(
|
||||
`
|
||||
INSERT INTO users(commitment, group_id)
|
||||
INSERT INTO commitments(commitment, group_id)
|
||||
VALUES ($1, $2)
|
||||
`,
|
||||
[commitment, groupId],
|
||||
@@ -79,7 +82,7 @@ export default class Database {
|
||||
) {
|
||||
await this.#pool.query(
|
||||
`
|
||||
UPDATE users(commitment, group_id)
|
||||
UPDATE commitments(commitment, group_id)
|
||||
SET commitment = $1
|
||||
WHERE group_id = $2 AND commitment = $3
|
||||
`,
|
||||
@@ -90,7 +93,7 @@ export default class Database {
|
||||
async getGroupInfo(groupId: string): Promise<GroupInfo | null> {
|
||||
const res = await this.#pool.query(
|
||||
`
|
||||
SELECT * FROM groups
|
||||
SELECT g.group_id, g.title, g.description, g.icon_url FROM groups g
|
||||
WHERE group_id = $1
|
||||
`,
|
||||
[groupId],
|
||||
@@ -102,11 +105,37 @@ export default class Database {
|
||||
async getGroups(): Promise<GroupInfo[]> {
|
||||
const res = await this.#pool.query(
|
||||
`
|
||||
SELECT * FROM groups
|
||||
SELECT g.group_id, g.title, g.description, g.icon_url FROM groups g
|
||||
`,
|
||||
[],
|
||||
);
|
||||
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
async getGroupMembers(groupId: string): Promise<GroupInfo[]> {
|
||||
const res = await this.#pool.query(
|
||||
`
|
||||
SELECT commitment FROM commitments
|
||||
WHERE group_id = $1
|
||||
`,
|
||||
[groupId],
|
||||
);
|
||||
|
||||
return res.rows.map(({ commitment }) => commitment);
|
||||
}
|
||||
|
||||
async getGroupsByCommitment(commitment: string): Promise<GroupInfo[]> {
|
||||
const res = await this.#pool.query(
|
||||
`
|
||||
SELECT g.group_id, g.title, g.description, g.icon_url FROM commitments
|
||||
LEFT OUTER JOIN groups g
|
||||
ON g.group_id = commitments.group_id
|
||||
WHERE commitment = $1
|
||||
`,
|
||||
[commitment],
|
||||
);
|
||||
|
||||
return res.rows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,37 @@ export enum StatusCode {
|
||||
ServiceUnavailable = 503,
|
||||
}
|
||||
|
||||
export enum HttpMethod {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
export type Request = http.IncomingMessage & {
|
||||
params?: { [key: string]: string };
|
||||
body?: any;
|
||||
};
|
||||
|
||||
export type Response = http.ServerResponse & {
|
||||
send(data?: any): void;
|
||||
};
|
||||
|
||||
export type Next = (request?: Request) => void;
|
||||
|
||||
export type RouteHandler = (
|
||||
request: Request,
|
||||
response: Response,
|
||||
next: Next,
|
||||
) => Promise<void> | void;
|
||||
|
||||
export default class HttpServer {
|
||||
#port: number;
|
||||
#handlers: [string, (req: Request, res: http.ServerResponse) => void][] = [];
|
||||
#handlers: [
|
||||
HttpMethod,
|
||||
string, // pattern
|
||||
RouteHandler[],
|
||||
][] = [];
|
||||
#server: http.Server;
|
||||
|
||||
constructor(options?: HttpServerOptions) {
|
||||
@@ -45,14 +69,36 @@ export default class HttpServer {
|
||||
});
|
||||
}
|
||||
|
||||
#onRequest = (req: Request, res: http.ServerResponse) => {
|
||||
#onRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
const url = parse(req.url || '', true);
|
||||
|
||||
for (const [pattern, handler] of this.#handlers) {
|
||||
for (const [method, pattern, handlers] of this.#handlers) {
|
||||
const params = testPath(pattern, url.pathname);
|
||||
if (params) {
|
||||
req.params = params;
|
||||
handler(req, res);
|
||||
|
||||
if (params && req.method === method) {
|
||||
let request = requestify(req, params);
|
||||
const response = responsify(res);
|
||||
|
||||
for (const handler of handlers) {
|
||||
request = await new Promise(async (resolve) => {
|
||||
try {
|
||||
await handler(
|
||||
request,
|
||||
response,
|
||||
// @ts-ignore
|
||||
resolve,
|
||||
);
|
||||
} catch (e) {
|
||||
response.writeHead(StatusCode.InternalServerError, {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
response.send(e.message);
|
||||
logger.error(e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -61,10 +107,55 @@ export default class HttpServer {
|
||||
res.end('not implememted');
|
||||
};
|
||||
|
||||
on = (
|
||||
path: string,
|
||||
handler: (req: Request, res: http.ServerResponse) => void,
|
||||
) => {
|
||||
this.#handlers.push([path, handler]);
|
||||
get = (path: string, ...handler: RouteHandler[]) => {
|
||||
this.#handlers.push([HttpMethod.GET, path, handler]);
|
||||
};
|
||||
|
||||
post = (path: string, ...handler: RouteHandler[]) => {
|
||||
this.#handlers.push([HttpMethod.POST, path, handler]);
|
||||
};
|
||||
}
|
||||
|
||||
function responsify(res: http.ServerResponse): Response {
|
||||
// @ts-ignore
|
||||
res.send = (data?: any) => {
|
||||
if (!data) {
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'text' });
|
||||
res.end('');
|
||||
} else if (typeof data === 'string') {
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'text' });
|
||||
res.end(data);
|
||||
} else {
|
||||
res.writeHead(StatusCode.Ok, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
return res as Response;
|
||||
}
|
||||
|
||||
function requestify(
|
||||
req: http.IncomingMessage,
|
||||
params?: { [key: string]: string },
|
||||
): Request {
|
||||
// @ts-ignore
|
||||
req.params = params;
|
||||
return req as Request;
|
||||
}
|
||||
|
||||
export function parseJsonBody(req: Request, res: Response, next: Next) {
|
||||
const requestBody: any[] = [];
|
||||
req.on('data', (chunks) => {
|
||||
requestBody.push(chunks);
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const parsedData = Buffer.concat(requestBody).toString();
|
||||
req.body = JSON.parse(parsedData);
|
||||
next(req);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
req.on('error', () => {});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as console from 'console';
|
||||
import * as console from "console";
|
||||
|
||||
const logger = {
|
||||
info: console.log.bind(console),
|
||||
error: console.error.bind(console),
|
||||
verbose: (...args: any[]) => {
|
||||
if (process.env.VERBOSE) {
|
||||
console.log(...args);
|
||||
|
||||
Reference in New Issue
Block a user