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 testtable (
 | 
			
		||||
	id text,
 | 
			
		||||
	somecolumn 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