//! TODO: There is like no error checking in queries at all //! TODO: Also no API? seriously? also when a form errors it just redirects with a 301 like everything is okay when it ISNT and it NEVER HAS BEEN import "dotenv/config"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime.js"; import express, { Express } from "express"; import bodyParser from "body-parser"; import session from "express-session"; import connectPgSimple from "connect-pg-simple"; import flash from "connect-flash"; import { drizzle, NodePgDatabase } from "drizzle-orm/node-postgres"; import { updates, users } from "./db/schema.js"; import { count, desc, eq } from "drizzle-orm"; // routes import adminRoutes from "./routes/admin.js"; import loginRoutes from "./routes/login.js"; import userRoutes from "./routes/users.js"; import updateRoutes from "./routes/updates.js"; import { createInviteCode, getMoods, render, render404, setNonce, UserStatus } from "./routes/util.js"; const db = drizzle(process.env.DATABASE_URL!); //! TODO: Make sure SQL queries arent being repeated too much (async () => { const { moods } = await getMoods(); // setup dayjs dayjs.extend(relativeTime); // setup express app const app = express(); app.set("view engine", "pug"); app.use(express.static("static")); app.use(bodyParser.urlencoded({ extended: false })); app.use( session({ store: new (connectPgSimple(session))(), secret: process.env.COOKIE_SECRET, resave: false, cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } }) ); app.use(flash()); //== Content Security Policy app.use((_, res, next) => { res.setHeader("X-Powered-By", "Lots and lots of milk"); res.setHeader( "Content-Security-Policy", "\ script-src 'nonce-" + setNonce() + "';\ object-src 'none'; base-uri 'none';" ); return next(); }); app.use((req, res, next) => { let s = ""; for (const [k, v] of Object.entries(req.query)) s += `${k}=${v}&`; s = s.slice(0, -1); console.log( `${req.ip.padEnd(24)} ${req.method.padStart(8)} ${req.path}${ s ? "?" + s : "" }` ); return next(); }); await init(app, db); app.get("/", async (req, res) => { const upd = db .selectDistinctOn([updates.user], { date: updates.date, user: users.name }) .from(updates) .innerJoin(users, eq(updates.user, users.id)) .orderBy(updates.user, desc(updates.date)) .limit(25) .as("upd"); const recentUpdates = await db .select() .from(upd) .orderBy(desc(upd.date)); const date = dayjs(); const feedUpdates = ( await db .select({ user: users.name, mood: updates.mood, date: updates.date, desc: updates.description }) .from(updates) .innerJoin(users, eq(updates.user, users.id)) .limit(50) .orderBy(desc(updates.date)) ).map((e) => { return { user: e.user, mood: moods[e.mood], date: e.date, relativeDate: date.to(dayjs(e.date)), desc: e.desc }; }); render(db, "index", "home", res, req, { users: (await db.select().from(users)).length, recentUpdates, feedUpdates }); }); adminRoutes(app, db); await userRoutes(app, db); await updateRoutes(app, db); loginRoutes(app, db); app.get("*", (req, res) => { render404(db, res, req); }); const path = process.env.LISTEN_ON_SOCKET === "true" ? `/run/user/${process.getuid()}/mipilin/mipilin.sock` : 1337; app.listen(path, () => { console.log(`mipilin is now listening on ${typeof path === "number" ? "https://127.0.0.1:" + path + "/" : path}!! Requests will be printed below. Good Luck`); }); })(); async function init(app: Express, db: NodePgDatabase) { const totalUsers = await db.select({ value: count() }).from(users); if (totalUsers[0].value === 0) { console.log( "There are no users registered. Creating a temporary invite code right now." ); console.log( " " + (await createInviteCode( db, 0, new Date(Date.now() + 10 * 1000 * 60), UserStatus.MODERATOR )) ); console.log( "This code expires in 10 minutes and will confer admin powers to whoever signs up with it." ); } }