feat: implementing server framework
This commit is contained in:
7
frontend/tsconfig.json
Normal file
7
frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": false,
|
||||||
|
"lib": [ "ES2021", "DOM", "DOM.Iterable" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { StoccaTreDbConn, WithoutId } from "./database.ts";
|
|
||||||
|
|
||||||
export type Ingredient = {
|
|
||||||
id: number,
|
|
||||||
name: string,
|
|
||||||
displayName: string,
|
|
||||||
displayNameDE: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class IngredientService {
|
|
||||||
private dbConnection: StoccaTreDbConn;
|
|
||||||
private mapById: Map<number, Ingredient> = new Map<number, Ingredient>();
|
|
||||||
private allGotten = false;
|
|
||||||
|
|
||||||
constructor(database: StoccaTreDbConn) {
|
|
||||||
this.dbConnection = database;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addIngredient(ingredient: WithoutId<Ingredient>): Promise<any> {
|
|
||||||
const result = await this.dbConnection.query<any>(
|
|
||||||
`INSERT INTO ingredients (id, name, displayName, displayNameDE) VALUES (NULL, '${ingredient.name}', '${ingredient.displayName}', '${ingredient.displayNameDE}');`
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllIngredients(): Promise<IterableIterator<Ingredient>> {
|
|
||||||
if (!this.allGotten) {
|
|
||||||
const result = await this.dbConnection.query<Ingredient[]>("SELECT * FROM ingredients");
|
|
||||||
result.forEach((ingredient) => this.mapById.set(ingredient.id, ingredient));
|
|
||||||
this.allGotten = true;
|
|
||||||
}
|
|
||||||
return this.mapById.values();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
server/StoccaTreRequest.ts
Normal file
6
server/StoccaTreRequest.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type HttpMethod = "POST" | "GET" | "PUT" | "DELETE";
|
||||||
|
|
||||||
|
export default interface StoccaTreRequest {
|
||||||
|
method: HttpMethod,
|
||||||
|
route: string,
|
||||||
|
}
|
||||||
5
server/StoccaTreRequestHandler.ts
Normal file
5
server/StoccaTreRequestHandler.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import StoccaTreRequest from "./StoccaTreRequest.ts";
|
||||||
|
|
||||||
|
export default interface StoccaTreRequestHandler {
|
||||||
|
handleRequest(request: StoccaTreRequest): Promise<Maybe<JSONObject>>,
|
||||||
|
}
|
||||||
6
server/config.ts
Normal file
6
server/config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
type ServerConfig = {
|
||||||
|
username: string,
|
||||||
|
hostname: string,
|
||||||
|
password: string,
|
||||||
|
port: number,
|
||||||
|
}
|
||||||
@@ -1,16 +1,40 @@
|
|||||||
import { Client } from "https://deno.land/x/mysql/mod.ts";
|
import { Client } from "https://deno.land/x/mysql/mod.ts";
|
||||||
import dbconfig from "./config.json" assert { type: "json" };
|
import config from "@/config.json" assert { type: "json" };
|
||||||
|
|
||||||
export type WithoutId<T> = Omit<T, "id">;
|
export type WithoutId<T> = Omit<T, "id">;
|
||||||
export interface StoccaTreDbConn {
|
export interface StoccaTreDbConn {
|
||||||
query<T>(query: string): Promise<T>;
|
query<T>(query: string): Promise<Maybe<T>>;
|
||||||
}
|
}
|
||||||
export default async function createNewDbConnection(): Promise<StoccaTreDbConn> {
|
export default async function createNewDbConnection(): Promise<StoccaTreDbConn> {
|
||||||
return await new Client().connect({
|
const mysqlClient = await new Client().connect({
|
||||||
hostname: dbconfig.hostname,
|
hostname: config.hostname,
|
||||||
username: dbconfig.username,
|
username: config.username,
|
||||||
db: "stocca_tre",
|
db: "stocca_tre",
|
||||||
password: dbconfig.password,
|
password: config.password,
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
query: async <T>(query: string): Promise<Maybe<T>> => {
|
||||||
|
let errMessage: string;
|
||||||
|
try {
|
||||||
|
const result = await mysqlClient.query(query);
|
||||||
|
return {
|
||||||
|
just: result as T,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (e && typeof (e as { message?: any }).message === "string") {
|
||||||
|
errMessage = (e as { message: string}).message;
|
||||||
|
} else {
|
||||||
|
errMessage = "An unknown error occurred in the database.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
just: null,
|
||||||
|
error: {
|
||||||
|
message: errMessage
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
server/globalTypes.d.ts
vendored
Normal file
20
server/globalTypes.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
type JSONValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| JSONObject
|
||||||
|
| JSONArray;
|
||||||
|
|
||||||
|
type JSONArray = Array<JSONValue>;
|
||||||
|
|
||||||
|
type Maybe<T> = {
|
||||||
|
just: T,
|
||||||
|
error: null,
|
||||||
|
} | {
|
||||||
|
just: null,
|
||||||
|
error: { message: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
interface JSONObject {
|
||||||
|
[x: string]: JSONValue;
|
||||||
|
}
|
||||||
5
server/import_map.json
Normal file
5
server/import_map.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"/": "./"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,59 @@
|
|||||||
import createNewDbConnection from "./database.ts";
|
import createNewDbConnection, {StoccaTreDbConn} from "/database.ts";
|
||||||
import IngredientService from "./Ingredient.ts";
|
import * as resources from "/resources/main.ts";
|
||||||
|
import config from "/config.json" assert { type: "json" };
|
||||||
|
import StoccaTreRequest, {HttpMethod} from "/StoccaTreRequest.ts";
|
||||||
|
import StoccaTreRequestHandler from "/StoccaTreRequestHandler.ts";
|
||||||
|
|
||||||
const server = Deno.listen({ port: 8080 });
|
const server = Deno.listen({ port: config.port ?? 8080 });
|
||||||
|
|
||||||
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
|
console.log(`Stocca Tre Server is running. Access it at: http://localhost:${ config.port }/`);
|
||||||
|
|
||||||
|
type StoccaTreApiBody = {
|
||||||
|
data: JSONObject,
|
||||||
|
error?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function newStoccaTreApiBody(): StoccaTreApiBody {
|
||||||
|
return {
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoccaTreServer = {
|
||||||
|
dbConnection: StoccaTreDbConn,
|
||||||
|
handleRequest(request: StoccaTreRequest): Promise<Maybe<JSONObject>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processRequest(server: StoccaTreServer, requestEvent: Deno.RequestEvent) {
|
||||||
|
const route = requestEvent.request.destination;
|
||||||
|
const requestMethod = requestEvent.request.method;
|
||||||
|
console.log(route, requestMethod);
|
||||||
|
const body: StoccaTreApiBody = newStoccaTreApiBody();
|
||||||
|
const maybeResult = await server.handleRequest({
|
||||||
|
route,
|
||||||
|
method: requestMethod as HttpMethod,
|
||||||
|
});
|
||||||
|
if (maybeResult.error) {
|
||||||
|
await requestEvent.respondWith(Response.json({ ...body, error: `Internal server error: ${maybeResult.error.message}` }, { status: 500 }));
|
||||||
|
} else {
|
||||||
|
body.data = maybeResult.just;
|
||||||
|
await requestEvent.respondWith(Response.json(body, { status: 200 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const database = await createNewDbConnection();
|
const database = await createNewDbConnection();
|
||||||
const ingredientService = new IngredientService(database);
|
const ingredientResource = new resources.IngredientResource(database);
|
||||||
|
|
||||||
for await (const conn of server) {
|
for await (const conn of server) {
|
||||||
serveHttp(conn);
|
await serveHttp(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function serveHttp(conn: Deno.Conn) {
|
async function serveHttp(conn: Deno.Conn) {
|
||||||
const httpConn = Deno.serveHttp(conn);
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
|
||||||
for await (const requestEvent of httpConn) {
|
for await (const requestEvent of httpConn) {
|
||||||
const body = [];
|
await processRequest({
|
||||||
for (const ingredient of (await ingredientService.getAllIngredients())) {
|
dbConnection: database,
|
||||||
body.push(ingredient);
|
handleRequest: (request: StoccaTreRequest) => ingredientResource.handleRequest(request),
|
||||||
}
|
}, requestEvent);
|
||||||
requestEvent.respondWith(Response.json(body, { status: 200 }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "stocca-tre-server",
|
"name": "stocca-tre-server",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "deno run --allow-net main.ts"
|
"start": "deno run --allow-net --import-map=importMap.json main.ts"
|
||||||
},
|
},
|
||||||
"description": "Backend for StoccaTre",
|
"description": "Backend for StoccaTre",
|
||||||
"author": "Daniel Ledda",
|
"author": "Daniel Ledda",
|
||||||
|
|||||||
35
server/resources/ingredient/IngredientCollection.ts
Normal file
35
server/resources/ingredient/IngredientCollection.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {StoccaTreDbConn, WithoutId} from "../../database.ts";
|
||||||
|
import {IngredientModel} from "./IngredientModel.ts";
|
||||||
|
|
||||||
|
export default class IngredientCollection {
|
||||||
|
private dbConnection: StoccaTreDbConn;
|
||||||
|
private mapById: Map<number, IngredientModel> = new Map<number, IngredientModel>();
|
||||||
|
private allGotten = false;
|
||||||
|
|
||||||
|
constructor(database: StoccaTreDbConn) {
|
||||||
|
this.dbConnection = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addIngredient(ingredient: WithoutId<IngredientModel>): Promise<any> {
|
||||||
|
const result = await this.dbConnection.query<any>(
|
||||||
|
`INSERT INTO ingredients (id, name, displayName, displayNameDE) VALUES (NULL, '${ingredient.name}', '${ingredient.displayName}', '${ingredient.displayNameDE}');`
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllIngredients(): Promise<Maybe<IterableIterator<IngredientModel>>> {
|
||||||
|
if (!this.allGotten) {
|
||||||
|
const result = await this.dbConnection.query<IngredientModel[]>("SELECT * FROM ingredients");
|
||||||
|
if (result.just) {
|
||||||
|
result.just.forEach((ingredient) => this.mapById.set(ingredient.id, ingredient));
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
this.allGotten = true;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
just: this.mapById.values(),
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
6
server/resources/ingredient/IngredientModel.ts
Normal file
6
server/resources/ingredient/IngredientModel.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type IngredientModel = {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
displayName: string,
|
||||||
|
displayNameDE: string,
|
||||||
|
};
|
||||||
30
server/resources/ingredient/IngredientResource.ts
Normal file
30
server/resources/ingredient/IngredientResource.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import {StoccaTreDbConn} from "/database.ts";
|
||||||
|
import IngredientCollection from "IngredientCollection.ts";
|
||||||
|
import StoccaTreRequest from "/StoccaTreRequest.ts";
|
||||||
|
|
||||||
|
export default class IngredientResource {
|
||||||
|
private dbConnection: StoccaTreDbConn;
|
||||||
|
private collection: IngredientCollection;
|
||||||
|
|
||||||
|
constructor(dbConnection: StoccaTreDbConn) {
|
||||||
|
this.dbConnection = dbConnection;
|
||||||
|
this.collection = new IngredientCollection(dbConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequest(request: StoccaTreRequest): Promise<Maybe<JSONObject>> {
|
||||||
|
return await this.allIngredients(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
async allIngredients(request: StoccaTreRequest): Promise<Maybe<JSONObject>> {
|
||||||
|
const getAllIngredientResult = await this.collection.getAllIngredients();
|
||||||
|
if (!getAllIngredientResult.error) {
|
||||||
|
return getAllIngredientResult.just;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
just: {
|
||||||
|
ingredients: Array.from(getAllIngredientResult.just),
|
||||||
|
},
|
||||||
|
error: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
3
server/resources/ingredient/main.ts
Normal file
3
server/resources/ingredient/main.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { type IngredientModel } from "./IngredientModel.ts";
|
||||||
|
export { default as IngredientCollection } from "./IngredientCollection.ts";
|
||||||
|
export { default as IngredientResource } from "./IngredientResource.ts";
|
||||||
1
server/resources/main.ts
Normal file
1
server/resources/main.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ingredient/main.ts";
|
||||||
7
server/tsconfig.json
Normal file
7
server/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": false,
|
||||||
|
"lib": [ "ES2021" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// "incremental": true, /* Enable incremental compilation */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
"target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
"module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
"module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
// "lib": [""], /* Specify library files to be included in the compilation. */
|
||||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
"allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
|||||||
Reference in New Issue
Block a user