Initial commit
This commit is contained in:
		
							
								
								
									
										34
									
								
								src/client/app.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/client/app.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import { css } from "@emotion/css"; | ||||
| import { Center, Cover, Stack } from "@firebox/components"; | ||||
|  | ||||
| const App = (props: { name: string }) => { | ||||
| 	const {name} = props; | ||||
| 	return ( | ||||
| 		<Stack> | ||||
| 			<div className={css`background-color: floralwhite;`}> | ||||
| 				<Cover gap pad> | ||||
| 					<Center> | ||||
| 						<Stack gap={-1}> | ||||
| 							<h1>Hello, {name}!</h1> | ||||
| 							<p>Welcome to a website with a certain design philosophy. Tell me how it's working out! I want to see this text wrap a few times. Hopefully this sentence will help.</p> | ||||
| 						</Stack> | ||||
| 					</Center> | ||||
| 					<Cover.Footer>A page by Dylan Pizzo</Cover.Footer> | ||||
| 				</Cover> | ||||
| 			</div> | ||||
| 			<div className={css`background-color: aliceblue;`}> | ||||
| 				<Cover gap pad> | ||||
| 					<Center> | ||||
| 						<Stack gap={-1}> | ||||
| 							<h1>Hello, {name}!</h1> | ||||
| 							<p>Welcome to a website with a certain design philosophy. Tell me how it's working out! I want to see this text wrap a few times. Hopefully this sentence will help.</p> | ||||
| 						</Stack> | ||||
| 					</Center> | ||||
| 					<Cover.Footer>A page by Dylan Pizzo</Cover.Footer> | ||||
| 				</Cover> | ||||
| 			</div> | ||||
| 		</Stack> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| export const app = <App name="World" />; | ||||
							
								
								
									
										6
									
								
								src/client/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/client/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import { createRoot } from "react-dom/client"; | ||||
| import { app } from "./app.js"; | ||||
|  | ||||
| const domNode = document.getElementById("root")!; | ||||
| const root = createRoot(domNode); | ||||
| root.render(app); | ||||
							
								
								
									
										28
									
								
								src/database/db.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/database/db.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import createConnectionPool, {ConnectionPool, ConnectionPoolConfig, sql} from '@databases/pg'; | ||||
|  | ||||
| export {sql}; | ||||
|  | ||||
| const portString = process.env["DB_PORT"]; | ||||
| const portNumber = portString ? parseInt(portString) : undefined; | ||||
|  | ||||
| const clientConfig: ConnectionPoolConfig = { | ||||
| 	host: process.env["DB_HOST"], | ||||
| 	user: process.env["DB_USER"], | ||||
| 	database: process.env["DB_NAME"], | ||||
| 	password: process.env["DB_PASSWORD"], | ||||
| 	port: portNumber, | ||||
| }; | ||||
|  | ||||
| // @ts-ignore | ||||
| const db: ConnectionPool = createConnectionPool({ | ||||
| 	connectionString: false, | ||||
| 	...clientConfig | ||||
| }); | ||||
|  | ||||
| process.once('SIGTERM', () => { | ||||
| 	db.dispose().catch((ex) => { | ||||
| 	 	console.error(ex); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| export {db}; | ||||
							
								
								
									
										4
									
								
								src/database/migrations/1-first-migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/database/migrations/1-first-migration.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| CREATE TABLE users ( | ||||
| 	id text, | ||||
| 	username text | ||||
| ) | ||||
							
								
								
									
										11
									
								
								src/server/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/server/api.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| import type { RouteList } from "./routelist.ts"  | ||||
|  | ||||
| type RouteUrl = RouteList[number]["url"]; | ||||
|  | ||||
| type HttpMethod = RouteList[number]["method"]; | ||||
|  | ||||
| type Route<M extends HttpMethod, U extends RouteUrl> = Extract<RouteList[number], {url: U, method: M}>; | ||||
|  | ||||
| export type RoutePayload<M extends HttpMethod, U extends RouteUrl> = Parameters<Route<M, U>["handler"]>[0]["payload"]; | ||||
|  | ||||
| export type RouteResponse<M extends HttpMethod, U extends RouteUrl> = Awaited<ReturnType<Route<M, U>["handler"]>>; | ||||
							
								
								
									
										18
									
								
								src/server/api/echo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/server/api/echo.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import { Type } from "@sinclair/typebox"; | ||||
| import { FirRouteInput, FirRouteOptions } from "../util/routewrap.js"; | ||||
|  | ||||
| const method = "GET"; | ||||
| const url = "/echo"; | ||||
|  | ||||
| const payloadT = Type.Any(); | ||||
|  | ||||
| const handler = ({payload}: FirRouteInput<typeof payloadT>) => { | ||||
| 	return payload; | ||||
| }; | ||||
|  | ||||
| export default { | ||||
| 	method, | ||||
| 	url, | ||||
| 	payloadT, | ||||
| 	handler, | ||||
| } as const satisfies FirRouteOptions<typeof payloadT>; | ||||
							
								
								
									
										1
									
								
								src/server/dbal/dbal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/server/dbal/dbal.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| // Database Access Layer stuff goes here | ||||
							
								
								
									
										31
									
								
								src/server/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/server/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // Import the framework and instantiate it | ||||
| import Fastify from 'fastify' | ||||
| import fastifyStatic from '@fastify/static' | ||||
| import { routeList } from "./routelist.ts"; | ||||
| import { route } from "./util/routewrap.ts"; | ||||
|  | ||||
| console.log(process.env["DATABASE_URL"]); | ||||
|  | ||||
| const server = Fastify({ | ||||
| 	logger: true | ||||
| }); | ||||
|  | ||||
| server.register(fastifyStatic, { | ||||
| 	root: new URL('public', import.meta.url).toString().slice("file://".length), | ||||
| 	prefix: '/', | ||||
| }); | ||||
|  | ||||
| routeList.forEach(firRoute => { | ||||
| 	server.route(route(firRoute)); | ||||
| }) | ||||
|  | ||||
| // Run the server! | ||||
| try { | ||||
| 	// Note: host needs to be 0.0.0.0 rather than omitted or localhost, otherwise | ||||
| 	// it always returns an empty reply when used inside docker... | ||||
| 	// See: https://github.com/fastify/fastify/issues/935 | ||||
| 	await server.listen({ port: parseInt(process.env["PORT"] ?? "3000"), host: "0.0.0.0" }) | ||||
| } catch (err) { | ||||
| 	server.log.error(err) | ||||
| 	process.exit(1) | ||||
| } | ||||
							
								
								
									
										48
									
								
								src/server/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/server/public/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| 	<head> | ||||
| 		<script src="dist/index.js" type="module"></script> | ||||
| 		<style> | ||||
| 			:root { | ||||
| 				--measure: 64ch; | ||||
| 				--ratio: 1.618; | ||||
|  | ||||
| 				--s-5: calc(var(--s-4) / var(--ratio)); | ||||
| 				--s-4: calc(var(--s-3) / var(--ratio)); | ||||
| 				--s-3: calc(var(--s-2) / var(--ratio)); | ||||
| 				--s-2: calc(var(--s-1) / var(--ratio)); | ||||
| 				--s-1: calc(var(--s0) / var(--ratio)); | ||||
| 				--s0: 1rem; | ||||
| 				--s1: calc(var(--s0) * var(--ratio)); | ||||
| 				--s2: calc(var(--s1) * var(--ratio)); | ||||
| 				--s3: calc(var(--s2) * var(--ratio)); | ||||
| 				--s4: calc(var(--s3) * var(--ratio)); | ||||
| 				--s5: calc(var(--s4) * var(--ratio)); | ||||
|  | ||||
| 				--border-radius: 0.5rem; | ||||
|  | ||||
| 				font-size: calc(1rem + 0.15vw); | ||||
| 				font-family: sans-serif; | ||||
| 			} | ||||
|  | ||||
| 			* { | ||||
| 				box-sizing: border-box; | ||||
| 				margin: 0; | ||||
| 				max-inline-size: var(--measure); | ||||
| 			} | ||||
|  | ||||
| 			html, | ||||
| 			body, | ||||
| 			div, | ||||
| 			header, | ||||
| 			nav, | ||||
| 			main, | ||||
| 			footer { | ||||
| 				max-inline-size: none; | ||||
| 			} | ||||
| 		</style> | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<div id="root"></div> | ||||
| 	</body> | ||||
| </html> | ||||
							
								
								
									
										7
									
								
								src/server/routelist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/server/routelist.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import echo from "./api/echo.ts"; | ||||
|  | ||||
| export const routeList = [ | ||||
| 	echo, | ||||
| ]; | ||||
|  | ||||
| export type RouteList = typeof routeList; | ||||
							
								
								
									
										47
									
								
								src/server/util/routewrap.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/server/util/routewrap.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import { Static, TSchema } from "@sinclair/typebox"; | ||||
| import { Value } from "@sinclair/typebox/value"; | ||||
| import { HTTPMethods } from "fastify" | ||||
| import { RouteOptions } from "fastify/types/route.js"; | ||||
|  | ||||
| type URLString = string; | ||||
|  | ||||
| export type FirRouteInput<TPayloadSchema extends TSchema> = { | ||||
| 	payload: Static<TPayloadSchema>, | ||||
| } | ||||
|  | ||||
| export type FirRouteOptions<TIn extends TSchema = TSchema, TOut extends TSchema = TSchema> = { | ||||
| 	method: HTTPMethods, | ||||
| 	url: URLString, | ||||
| 	payloadT: TIn, | ||||
| 	responseT?: TOut, | ||||
| 	handler: (input: FirRouteInput<TIn>) => Static<TOut> | Promise<Static<TOut>>, | ||||
| } | ||||
|  | ||||
| export const route = <TIn extends TSchema, TOut extends TSchema>(routeOptions: FirRouteOptions<TIn, TOut>): RouteOptions => { | ||||
| 	const { | ||||
| 		method, | ||||
| 		url, | ||||
| 		payloadT, | ||||
| 		handler, | ||||
| 	} = routeOptions; | ||||
|  | ||||
| 	const augmentedHandler = (request: Parameters<RouteOptions["handler"]>[0]) => { | ||||
| 		const { | ||||
| 			body, | ||||
| 			query, | ||||
| 		} = request; | ||||
| 		const payload = body ?? query; | ||||
| 		if (Value.Check(payloadT, payload)) { | ||||
| 			return handler({payload}); | ||||
| 		} else { | ||||
| 			 | ||||
| 			throw new Error("Payload wrong shape."); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return { | ||||
| 		method, | ||||
| 		url, | ||||
| 		handler: augmentedHandler, | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user