feat: custom server that works
This commit is contained in:
parent
2e86e827ca
commit
e151a91290
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ next-env.d.ts
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/dist
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Config } from "drizzle-kit";
|
import type { Config } from "drizzle-kit";
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
schema: "./src/server/db/schema.ts",
|
schema: "./src/server/db/schema.ts",
|
||||||
driver: "better-sqlite",
|
driver: "better-sqlite",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: env.DATABASE_PATH,
|
url: process.env.DATABASE_PATH ?? "./data/db.sqlite",
|
||||||
},
|
},
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
/**
|
|
||||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
|
||||||
* for Docker builds.
|
|
||||||
*/
|
|
||||||
await import("./src/env.mjs");
|
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
experimental: {
|
experimental: {
|
||||||
|
|
43
package.json
43
package.json
|
@ -2,15 +2,20 @@
|
||||||
"name": "hostforge",
|
"name": "hostforge",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build:next": "next build",
|
||||||
|
"build:server-tsc": "tsc -p tsconfig.server.json && tscpaths -p tsconfig.server.json -s ./src -o ./dist/",
|
||||||
|
"build:server": "tsup",
|
||||||
|
"clean": "rm -rf .next dist",
|
||||||
"db:push": "drizzle-kit push:sqlite",
|
"db:push": "drizzle-kit push:sqlite",
|
||||||
"dev": "tsx watch src/server/server.ts",
|
"dev": "npm-run-all build:server dev:run",
|
||||||
|
"dev:run": "node --enable-source-maps dist/server.js",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"start": "next start"
|
"start": "node -r tsconfig"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/form": "^7.1.7",
|
"@mantine/form": "^7.2.1",
|
||||||
"@prisma/migrate": "^5.5.2",
|
"@prisma/migrate": "^5.5.2",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
@ -32,14 +37,15 @@
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"drizzle-orm": "^0.28.6",
|
"drizzle-orm": "^0.28.6",
|
||||||
|
"extensionless": "^1.7.3",
|
||||||
"ipaddr.js": "^2.1.0",
|
"ipaddr.js": "^2.1.0",
|
||||||
"next": "^14.0.1",
|
"next": "14.0.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"node-os-utils": "^1.3.7",
|
"node-os-utils": "^1.3.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"recharts": "^2.9.2",
|
"recharts": "^2.9.3",
|
||||||
"sonner": "^1.2.0",
|
"sonner": "^1.2.0",
|
||||||
"superjson": "^2.2.0",
|
"superjson": "^2.2.0",
|
||||||
"tailwind-merge": "^2.0.0",
|
"tailwind-merge": "^2.0.0",
|
||||||
|
@ -51,26 +57,31 @@
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.6",
|
"@types/better-sqlite3": "^7.6.7",
|
||||||
"@types/bunyan": "^1.8.10",
|
"@types/bunyan": "^1.8.11",
|
||||||
"@types/bunyan-format": "^0.2.7",
|
"@types/bunyan-format": "^0.2.8",
|
||||||
"@types/eslint": "^8.44.6",
|
"@types/eslint": "^8.44.7",
|
||||||
"@types/node": "^20.8.10",
|
"@types/node": "^20.9.0",
|
||||||
"@types/node-os-utils": "^1.3.4",
|
"@types/node-os-utils": "^1.3.4",
|
||||||
"@types/react": "^18.2.35",
|
"@types/react": "^18.2.37",
|
||||||
"@types/react-dom": "^18.2.14",
|
"@types/react-dom": "^18.2.15",
|
||||||
"@types/ua-parser-js": "^0.7.38",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@types/ws": "^8.5.9",
|
"@types/ws": "^8.5.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||||
"@typescript-eslint/parser": "^6.9.1",
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"drizzle-kit": "^0.19.13",
|
"drizzle-kit": "^0.19.13",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-next": "^14.0.1",
|
"eslint-config-next": "^14.0.1",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
"tailwindcss": "^3.3.5",
|
"tailwindcss": "^3.3.5",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"tscpaths": "^0.0.9",
|
||||||
|
"tsup": "^7.2.0",
|
||||||
"tsx": "^3.14.0",
|
"tsx": "^3.14.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
|
|
1496
pnpm-lock.yaml
1496
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
import { type NextRequest } from "next/server";
|
import { type NextRequest } from "next/server.js";
|
||||||
|
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env";
|
||||||
import { appRouter } from "~/server/api/root";
|
import { appRouter } from "~/server/api/root";
|
||||||
import { createTRPCContext } from "~/server/api/trpc";
|
import { createTRPCContext } from "~/server/api/trpc";
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { RiPulseFill } from "react-icons/ri";
|
|
||||||
import { ResponsiveContainer, AreaChart, Area } from "recharts";
|
import { ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||||
import styles from "./StatCard.module.css";
|
// import styles from "./StatCard.module.css";
|
||||||
|
|
||||||
export function StatCard<T extends { [key: string]: number }>(props: {
|
export function StatCard<T extends Record<string, number>>(props: {
|
||||||
title: string;
|
title: string;
|
||||||
value: string;
|
value: string;
|
||||||
subvalue: string;
|
subvalue: string;
|
||||||
|
@ -73,7 +72,7 @@ export function StatCard<T extends { [key: string]: number }>(props: {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`relative z-10 ${styles["stat-card"]}`}>
|
<div className={`relative z-10 ${/*styles["stat-card"]*/ "asdf"}`}>
|
||||||
<p className="stroke stroke-card text-2xl font-bold">{props.value}</p>
|
<p className="stroke stroke-card text-2xl font-bold">{props.value}</p>
|
||||||
<p className="stroke stroke-card text-sm text-muted-foreground">
|
<p className="stroke stroke-card text-sm text-muted-foreground">
|
||||||
{props.subvalue}
|
{props.subvalue}
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const env = createEnv({
|
||||||
* isn't built with invalid env vars.
|
* isn't built with invalid env vars.
|
||||||
*/
|
*/
|
||||||
server: {
|
server: {
|
||||||
|
// make sure to update drizzle.config.js if you change this
|
||||||
DATABASE_PATH: z.string().default("./data/db.sqlite"),
|
DATABASE_PATH: z.string().default("./data/db.sqlite"),
|
||||||
|
|
||||||
NODE_ENV: z
|
NODE_ENV: z
|
||||||
|
@ -16,7 +17,10 @@ export const env = createEnv({
|
||||||
SQLITE_UUIDV7_EXT_PATH: z.string().optional(),
|
SQLITE_UUIDV7_EXT_PATH: z.string().optional(),
|
||||||
SESSION_SECRET: z.string().min(8),
|
SESSION_SECRET: z.string().min(8),
|
||||||
HOSTNAME: z.string().default("localhost"),
|
HOSTNAME: z.string().default("localhost"),
|
||||||
PORT: z.number().default(3000),
|
PORT: z
|
||||||
|
.string()
|
||||||
|
.default("3000")
|
||||||
|
.transform((str) => parseInt(str)),
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,6 +1,7 @@
|
||||||
import pacakge from "../package.json";
|
// import pacakge from "../package.json";
|
||||||
import { env } from "./env.mjs";
|
import { env } from "~/env";
|
||||||
const { version } = pacakge;
|
// const { version } = pacakge;
|
||||||
|
const version = "1.0.0";
|
||||||
|
|
||||||
export async function register() {
|
export async function register() {
|
||||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||||
|
@ -8,15 +9,17 @@ export async function register() {
|
||||||
const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
|
const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
|
||||||
const { db } = await import("./server/db");
|
const { db } = await import("./server/db");
|
||||||
const { mkdir, stat } = await import("fs/promises");
|
const { mkdir, stat } = await import("fs/promises");
|
||||||
const { dirname } = await import("path");
|
const path = await import("path");
|
||||||
|
|
||||||
// check if database folder exists
|
// check if database folder exists
|
||||||
try {
|
try {
|
||||||
const dir = dirname(env.DATABASE_PATH);
|
const dir = path.dirname(env.DATABASE_PATH);
|
||||||
await stat(dir);
|
await stat(dir);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await mkdir(dirname(env.DATABASE_PATH), { recursive: true });
|
await mkdir(path.dirname(env.DATABASE_PATH), { recursive: true });
|
||||||
logger.debug(`Created database folder ${dirname(env.DATABASE_PATH)}`);
|
logger.debug(
|
||||||
|
`Created database folder ${path.dirname(env.DATABASE_PATH)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.NODE_ENV === "production") {
|
if (env.NODE_ENV === "production") {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* need to use are documented accordingly near the end.
|
* need to use are documented accordingly near the end.
|
||||||
*/
|
*/
|
||||||
import { TRPCError, initTRPC } from "@trpc/server";
|
import { TRPCError, initTRPC } from "@trpc/server";
|
||||||
import { type NextRequest } from "next/server";
|
import { type NextRequest } from "next/server.js";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { db } from "../db";
|
||||||
import { users, sessions } from "../db/schema";
|
import { users, sessions } from "../db/schema";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { NextRequest, userAgent } from "next/server";
|
import { NextRequest, userAgent } from "next/server.js";
|
||||||
import { hash as argon2Hash } from "argon2";
|
import { hash as argon2Hash } from "argon2";
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env";
|
||||||
|
|
||||||
export type SessionUpdateData = Partial<{
|
export type SessionUpdateData = Partial<{
|
||||||
ua: string;
|
ua: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||||
import SQLite3 from "better-sqlite3";
|
import SQLite3 from "better-sqlite3";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env";
|
||||||
|
|
||||||
const sqlite = new SQLite3(env.DATABASE_PATH);
|
const sqlite = new SQLite3(env.DATABASE_PATH);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
// import { config } from "dotenv";
|
||||||
|
// config();
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
|
||||||
import next from "next";
|
import next from "next";
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env";
|
||||||
import { createServer } from "http";
|
import { createServer } from "http";
|
||||||
import logger from "./utils/logger";
|
import logger from "./utils/logger";
|
||||||
import ws from "ws";
|
// import ws from "ws";
|
||||||
|
import { WebSocketServer } from "ws";
|
||||||
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
||||||
import { appRouter } from "./api/root";
|
import { appRouter } from "./api/root";
|
||||||
import { createTRPCContext } from "./api/trpc";
|
import { createTRPCContext } from "./api/trpc";
|
||||||
|
@ -16,6 +18,9 @@ async function startApp() {
|
||||||
dev: env.NODE_ENV !== "production",
|
dev: env.NODE_ENV !== "production",
|
||||||
hostname: env.HOSTNAME,
|
hostname: env.HOSTNAME,
|
||||||
port: env.PORT,
|
port: env.PORT,
|
||||||
|
// dir: path.join(__dirname, "../.."),
|
||||||
|
customServer: true,
|
||||||
|
isNodeDebugging: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.prepare();
|
await app.prepare();
|
||||||
|
@ -26,18 +31,15 @@ async function startApp() {
|
||||||
|
|
||||||
// create the http server
|
// create the http server
|
||||||
const server = createServer((req, res) => {
|
const server = createServer((req, res) => {
|
||||||
try {
|
getHandler(req, res).catch((error) => {
|
||||||
// handle the request
|
|
||||||
getHandler(req, res);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end("Internal Server Error");
|
res.end("Internal Server Error");
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// create the websocket server
|
// create the websocket server
|
||||||
const wss = new ws.Server({ noServer: true });
|
const wss = new WebSocketServer({ noServer: true });
|
||||||
const trpcHandler = applyWSSHandler({
|
const trpcHandler = applyWSSHandler({
|
||||||
wss,
|
wss,
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
|
@ -49,6 +51,7 @@ async function startApp() {
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
|
logger.warn("SIGTERM received, shutting down...");
|
||||||
trpcHandler.broadcastReconnectNotification();
|
trpcHandler.broadcastReconnectNotification();
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -59,9 +62,9 @@ async function startApp() {
|
||||||
server.on("upgrade", (req, socket, head) => {
|
server.on("upgrade", (req, socket, head) => {
|
||||||
// send trpc requests to the trpc server
|
// send trpc requests to the trpc server
|
||||||
if (req.url?.startsWith("/api/trpc")) {
|
if (req.url?.startsWith("/api/trpc")) {
|
||||||
wss.handleUpgrade(req, socket, head, () => {});
|
wss.handleUpgrade(req, socket, head, () => undefined);
|
||||||
} else {
|
} else {
|
||||||
upgradeHandler(req, socket, head);
|
void upgradeHandler(req, socket, head);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,4 +74,4 @@ async function startApp() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startApp();
|
void startApp();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { IncomingMessage } from "http";
|
import { type IncomingMessage } from "http";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns an node:http IncomingMessage into a next.js request
|
* Turns an node:http IncomingMessage into a next.js request
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "ES2020",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"noEmit": false
|
"moduleResolution": "node",
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"isolatedModules": false,
|
||||||
|
"noEmit": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*"]
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "src/server/**/*.ts", "src/server/**/*.tsx"]
|
||||||
}
|
}
|
||||||
|
|
17
tsup.config.ts
Normal file
17
tsup.config.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { defineConfig, type Options } from "tsup";
|
||||||
|
|
||||||
|
const opts: Options = {
|
||||||
|
platform: "node",
|
||||||
|
format: ["esm"],
|
||||||
|
treeshake: true,
|
||||||
|
clean: true,
|
||||||
|
sourcemap: true,
|
||||||
|
tsconfig: "tsconfig.server.json",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
entryPoints: ["src/server/server.ts"],
|
||||||
|
...opts,
|
||||||
|
},
|
||||||
|
]);
|
Loading…
Reference in a new issue