init: add code
This commit is contained in:
commit
79c4323666
26 changed files with 3684 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use_nix
|
||||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# prod
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# dev
|
||||||
|
.yarn/
|
||||||
|
!.yarn/releases
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/usage.statistics.xml
|
||||||
|
.idea/shelf
|
||||||
|
|
||||||
|
# deps
|
||||||
|
node_modules/
|
||||||
|
.wrangler
|
||||||
|
|
||||||
|
# env
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
.dev.vars
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
/src/generated/prisma
|
||||||
12
README.md
Normal file
12
README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# anum
|
||||||
|
|
||||||
|
the unholy lovechild of Xitter and 4chan
|
||||||
|
|
||||||
|
## migration tutorial because i have dementia
|
||||||
|
### if this makes it to prod im going to follow lowtiergod's advice
|
||||||
|
1. `cp prisma/schema.prisma prisma/schema.prisma.old`
|
||||||
|
2. touch schema.prisma like diddy touches children
|
||||||
|
3. tell wrangler that we are Migrationing It
|
||||||
|
4. get prisma to create a migration from the old ass motherfucker schema to the new babyoiled one and throw its output in wrangler's migration template
|
||||||
|
5. tell wrangler that we have Migrationed It go apply this shit now you stinky ass motherfucker blackout revival flare looking ass
|
||||||
|
6. `rm prisma/schema.prisma.old`
|
||||||
166
ai_seed.sql
Normal file
166
ai_seed.sql
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- 1. CLEANUP (Optional: Remove if you want to keep existing data)
|
||||||
|
DELETE FROM "Post";
|
||||||
|
DELETE FROM "User";
|
||||||
|
DELETE FROM "sqlite_sequence"; -- Resets autoincrement counters
|
||||||
|
|
||||||
|
-- 2. CREATE USERS
|
||||||
|
-- (Passwords are all the same dummy bcrypt hash for "password123")
|
||||||
|
INSERT INTO "User" (id, email, name, display_name, password) VALUES
|
||||||
|
(1, 'admin@forum.com', 'admin', 'SysAdmin', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(2, 'steve@moderator.com', 'mod_steve', 'Steve [MOD]', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(3, 'jane.doe@gmail.com', 'janedoe', 'Jane D.', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(4, 'rust_evangelist@tech.com', 'rusty', 'RustInPeace', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(5, 'cpp_veteran@legacy.com', 'segfault', 'C++ Grandmaster', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(6, 'troll123@hotmail.com', 'u_mad_bro', 'Anonymous', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(7, 'helpme@student.edu', 'noob_coder', 'HelpMePls', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(8, 'linux_fan@gnu.org', 'sudo_make_me', 'Arch User', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(9, 'webdev@agency.com', 'div_center', 'CSS Wizard', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(10, 'gamer@twitch.tv', 'xx_sniper_xx', 'Pro Gamer', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(11, 'bot@bot.com', 'news_bot', 'News Aggregator', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(12, 'lurker@yahoo.com', 'lurker007', NULL, '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(13, 'vim@editor.com', 'vim_enthusiast', ':wq', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(14, 'emacs@editor.com', 'emacs_guru', 'M-x Butterfly', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7'),
|
||||||
|
(15, 'react@fb.com', 'hook_master', 'UseEffect', '$2b$10$EpWaTgiK/H/t/0W.p/h.quu/L7C7jQe5nVqg.j/z.p/h.quu/L7C7');
|
||||||
|
|
||||||
|
|
||||||
|
-- 3. CREATE THREADS (Top level posts)
|
||||||
|
-- We manually assign IDs here to ensure the replies attach correctly later.
|
||||||
|
|
||||||
|
-- Thread 1: Welcome (Sticky)
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(1, 'Welcome to the Community! READ FIRST', 'Welcome everyone. Please be nice. No spamming. Enjoy your stay!', 1, NULL, datetime('now', '-30 days'));
|
||||||
|
|
||||||
|
-- Thread 2: Flame war (Tabs vs Spaces)
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(2, 'Unpopular Opinion: Spaces are objectively better than Tabs', 'I said what I said. If you use tabs, your formatting is at the mercy of the editor settings. Discuss.', 3, NULL, datetime('now', '-15 days'));
|
||||||
|
|
||||||
|
-- Thread 3: Tech Support
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(3, 'URGENT: Cannot exit Vim?', 'I have been stuck in Vim for 4 days. I have tried Esc, Ctrl+C, Alt+F4. I am starving. Please help.', 7, NULL, datetime('now', '-2 days'));
|
||||||
|
|
||||||
|
-- Thread 4: Gaming
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(4, 'Elden Ring DLC discussion', 'Has anyone beaten the final boss yet? No spoilers please, but that second phase is brutal.', 10, NULL, datetime('now', '-5 days'));
|
||||||
|
|
||||||
|
-- Thread 5: Programming Language War
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(5, 'Why Rust will replace C++ in 5 years', 'Memory safety is not optional anymore. The borrow checker is your friend.', 4, NULL, datetime('now', '-10 days'));
|
||||||
|
|
||||||
|
-- Thread 6: Off Topic Megathread (This will house most of the "volume")
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(6, 'MEGATHREAD: What are you listening to right now?', 'Post your current jam. Keep it clean.', 2, NULL, datetime('now', '-60 days'));
|
||||||
|
|
||||||
|
-- Thread 7: Random
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(7, 'My cat walked on my keyboard and deployed to production', 'Fortunately, the tests passed. I think my cat is a better dev than me.', 9, NULL, datetime('now', '-1 day'));
|
||||||
|
|
||||||
|
-- Thread 8: News
|
||||||
|
INSERT INTO "Post" (id, title, content, userId, parentId, createdAt) VALUES
|
||||||
|
(8, 'Tech News: AI takes over toaster industry', 'Smart toasters are now requiring a subscription to toast bagels.', 11, NULL, datetime('now', '-20 days'));
|
||||||
|
|
||||||
|
|
||||||
|
-- 4. CREATE REALISTIC REPLIES
|
||||||
|
-- Thread 1 (Welcome) Replies
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Re: Welcome', 'Thanks admin! Glad to be here.', 3, 1, datetime('now', '-29 days')),
|
||||||
|
('Re: Welcome', 'First!', 6, 1, datetime('now', '-29 days')),
|
||||||
|
('Re: Welcome', 'Please do not post "First", this is not YouTube.', 2, 1, datetime('now', '-29 days')),
|
||||||
|
('Re: Welcome', 'Hello world.', 5, 1, datetime('now', '-28 days'));
|
||||||
|
|
||||||
|
-- Thread 2 (Tabs vs Spaces) - The Fight
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Re: Tabs vs Spaces', 'You are wrong. Tabs save file size. Imagine a 1MB file, tabs save bytes!', 5, 2, datetime('now', '-14 days')),
|
||||||
|
('Re: Tabs vs Spaces', 'Storage is cheap. Consistency is priceless. Spaces all the way.', 9, 2, datetime('now', '-14 days')),
|
||||||
|
('Re: Tabs vs Spaces', 'I use a mix of both just to annoy my coworkers.', 6, 2, datetime('now', '-14 days')),
|
||||||
|
('Re: Tabs vs Spaces', 'You monster.', 3, 2, datetime('now', '-13 days')),
|
||||||
|
('Re: Tabs vs Spaces', 'Go style guide says tabs. I follow the Go style guide.', 4, 2, datetime('now', '-13 days')),
|
||||||
|
('Re: Tabs vs Spaces', 'Python enforces indentation. Spaces are the standard there.', 8, 2, datetime('now', '-13 days'));
|
||||||
|
|
||||||
|
-- Thread 3 (Vim Help)
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Re: Vim', 'Type :q! and hit enter.', 13, 3, datetime('now', '-2 days')),
|
||||||
|
('Re: Vim', 'Or just unplug your computer.', 6, 3, datetime('now', '-2 days')),
|
||||||
|
('Re: Vim', 'Just switch to Emacs, we have a menu bar.', 14, 3, datetime('now', '-2 days')),
|
||||||
|
('Re: Vim', 'OMG THANK YOU @vim_enthusiast you saved my life.', 7, 3, datetime('now', '-1 day'));
|
||||||
|
|
||||||
|
-- Thread 5 (Rust vs C++)
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Re: Rust', 'C++ is still faster if you know what you are doing.', 5, 5, datetime('now', '-9 days')),
|
||||||
|
('Re: Rust', 'Most people do not know what they are doing though.', 4, 5, datetime('now', '-9 days')),
|
||||||
|
('Re: Rust', 'Rewriting everything in Rust is not feasible for large legacy codebases.', 1, 5, datetime('now', '-8 days')),
|
||||||
|
('Re: Rust', 'Agreed, but for greenfield projects it is a no brainer.', 9, 5, datetime('now', '-8 days')),
|
||||||
|
('Re: Rust', 'I like Zig.', 8, 5, datetime('now', '-7 days'));
|
||||||
|
|
||||||
|
-- Thread 7 (Cat deployment)
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Re: Cat', 'Is the cat looking for a job? We are hiring.', 1, 7, datetime('now', '-23 hours')),
|
||||||
|
('Re: Cat', 'Pics or it didnt happen.', 6, 7, datetime('now', '-20 hours')),
|
||||||
|
('Re: Cat', 'Mine just sleeps on the warm laptop vent.', 15, 7, datetime('now', '-10 hours'));
|
||||||
|
|
||||||
|
|
||||||
|
-- 5. GENERATE BULK VOLUME (The "Megathread")
|
||||||
|
-- We use a recursive query or just a massive block of inserts to hit ~500 posts.
|
||||||
|
-- We will simulate a "What are you listening to" thread (Parent ID 6).
|
||||||
|
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt) VALUES
|
||||||
|
('Song', 'Pink Floyd - Echoes', 8, 6, datetime('now', '-59 days')),
|
||||||
|
('Song', 'Daft Punk - Discovery (Whole Album)', 9, 6, datetime('now', '-58 days')),
|
||||||
|
('Song', 'Lo-fi beats to study to', 7, 6, datetime('now', '-58 days')),
|
||||||
|
('Song', 'Metallica - Master of Puppets', 5, 6, datetime('now', '-57 days')),
|
||||||
|
('Song', 'Darude - Sandstorm', 6, 6, datetime('now', '-56 days')),
|
||||||
|
('Song', 'Rick Astley - Never Gonna Give You Up', 6, 6, datetime('now', '-56 days')),
|
||||||
|
('Song', 'Bach - Cello Suite No. 1', 3, 6, datetime('now', '-55 days')),
|
||||||
|
('Song', 'Kendrick Lamar - DNA', 15, 6, datetime('now', '-54 days')),
|
||||||
|
('Song', 'Tool - Lateralus', 4, 6, datetime('now', '-53 days')),
|
||||||
|
('Song', 'Taylor Swift', 10, 6, datetime('now', '-52 days')),
|
||||||
|
('Song', '+1 for Tool', 8, 6, datetime('now', '-52 days')),
|
||||||
|
('Song', 'Radiohead - OK Computer', 2, 6, datetime('now', '-50 days')),
|
||||||
|
('Song', 'Video Game OSTs mostly', 10, 6, datetime('now', '-49 days')),
|
||||||
|
('Song', 'Aphex Twin', 11, 6, datetime('now', '-48 days')),
|
||||||
|
('Song', 'Silence. My fans are too loud.', 12, 6, datetime('now', '-47 days')),
|
||||||
|
('Song', 'Jazz vibes', 14, 6, datetime('now', '-46 days'));
|
||||||
|
|
||||||
|
-- To get to 500 without writing 500 lines manually, we will use a cross join trick
|
||||||
|
-- to multiply these generic comments into the megathread (ID 6).
|
||||||
|
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt)
|
||||||
|
SELECT
|
||||||
|
'Re: Music',
|
||||||
|
'Listening to track #' || (ABS(RANDOM()) % 1000),
|
||||||
|
(ABS(RANDOM()) % 15) + 1, -- Random User ID 1-15
|
||||||
|
6, -- Parent ID 6 (Music Thread)
|
||||||
|
datetime('now', '-' || (ABS(RANDOM()) % 60) || ' days')
|
||||||
|
FROM "User" u1, "User" u2, "User" u3
|
||||||
|
LIMIT 300; -- Adds 300 generic posts
|
||||||
|
|
||||||
|
-- Add some random replies to the Rust vs C++ thread (ID 5) to make it look heated
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt)
|
||||||
|
SELECT
|
||||||
|
'Re: Rust vs C++',
|
||||||
|
CASE (ABS(RANDOM()) % 5)
|
||||||
|
WHEN 0 THEN 'I disagree strongly.'
|
||||||
|
WHEN 1 THEN 'This is the way.'
|
||||||
|
WHEN 2 THEN 'Have you read the documentation?'
|
||||||
|
WHEN 3 THEN 'Benchmarks prove otherwise.'
|
||||||
|
ELSE 'Interesting point.'
|
||||||
|
END,
|
||||||
|
(ABS(RANDOM()) % 15) + 1,
|
||||||
|
5, -- Parent ID 5
|
||||||
|
datetime('now', '-' || (ABS(RANDOM()) % 10) || ' days')
|
||||||
|
FROM "User" u1, "User" u2
|
||||||
|
LIMIT 50;
|
||||||
|
|
||||||
|
-- Add some "Bump" posts to random threads
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt)
|
||||||
|
SELECT
|
||||||
|
'Bump',
|
||||||
|
'Bumping this thread.',
|
||||||
|
(ABS(RANDOM()) % 15) + 1,
|
||||||
|
(ABS(RANDOM()) % 8) + 1, -- Random Parent ID 1-8
|
||||||
|
datetime('now', '-1 hour')
|
||||||
|
FROM "User"
|
||||||
|
LIMIT 20;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
9
eslint.config.ts
Normal file
9
eslint.config.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.node } },
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
]);
|
||||||
59
flake.lock
generated
Normal file
59
flake.lock
generated
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767364772,
|
||||||
|
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
15
flake.nix
Normal file
15
flake.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
description = "anum backend";
|
||||||
|
|
||||||
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem
|
||||||
|
(system:
|
||||||
|
let pkgs = nixpkgs.legacyPackages.${system}; in
|
||||||
|
{
|
||||||
|
devShells.default = import ./shell.nix { inherit pkgs; };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
23
migrations/0001_init.sql
Normal file
23
migrations/0001_init.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"display_name" TEXT,
|
||||||
|
"password" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Post" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_name_key" ON "User"("name");
|
||||||
17
migrations/0002_replies.sql
Normal file
17
migrations/0002_replies.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Post" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"parentId" INTEGER,
|
||||||
|
CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Post_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Post" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Post" ("content", "id", "title", "userId") SELECT "content", "id", "title", "userId" FROM "Post";
|
||||||
|
DROP TABLE "Post";
|
||||||
|
ALTER TABLE "new_Post" RENAME TO "Post";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
18
migrations/0003_post_creation_date.sql
Normal file
18
migrations/0003_post_creation_date.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Post" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"parentId" INTEGER,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Post_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Post" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Post" ("content", "id", "parentId", "title", "userId") SELECT "content", "id", "parentId", "title", "userId" FROM "Post";
|
||||||
|
DROP TABLE "Post";
|
||||||
|
ALTER TABLE "new_Post" RENAME TO "Post";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
1
migrations/migration_lock.toml
Normal file
1
migrations/migration_lock.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
provider = "sqlite"
|
||||||
28
package.json
Normal file
28
package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "anum",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "wrangler dev",
|
||||||
|
"deploy": "wrangler deploy --minify",
|
||||||
|
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/zod-validator": "^0.7.6",
|
||||||
|
"@prisma/adapter-d1": "^7.2.0",
|
||||||
|
"@prisma/client": "^7.2.0",
|
||||||
|
"bcrypt-ts": "^8.0.0",
|
||||||
|
"hono": "^4.11.3",
|
||||||
|
"jose": "^6.1.3",
|
||||||
|
"zod": "^4.3.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"globals": "^17.0.0",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"prisma": "^7.2.0",
|
||||||
|
"typescript-eslint": "^8.51.0",
|
||||||
|
"vscode-langservers-extracted": "^4.10.0",
|
||||||
|
"wrangler": "^4.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
2782
pnpm-lock.yaml
generated
Normal file
2782
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
14
prisma.config.ts
Normal file
14
prisma.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// This file was generated by Prisma, and assumes you have installed the following:
|
||||||
|
// npm install --save-dev prisma dotenv
|
||||||
|
import "dotenv/config";
|
||||||
|
import { defineConfig } from "prisma/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "prisma/schema.prisma",
|
||||||
|
migrations: {
|
||||||
|
path: "prisma/migrations",
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: process.env["DATABASE_URL"],
|
||||||
|
},
|
||||||
|
});
|
||||||
31
prisma/schema.prisma
Normal file
31
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
email String @unique
|
||||||
|
name String @unique
|
||||||
|
display_name String?
|
||||||
|
password String
|
||||||
|
posts Post[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Post {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
title String
|
||||||
|
content String
|
||||||
|
author User @relation(fields: [userId], references: [id])
|
||||||
|
userId Int
|
||||||
|
replies Post[] @relation("PostReplies")
|
||||||
|
parent Post? @relation("PostReplies", fields: [parentId], references: [id])
|
||||||
|
parentId Int?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
48
seed.sql
Normal file
48
seed.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
-- 1. Generate 100 Users
|
||||||
|
-- Password is a bcrypt hash for "password123"
|
||||||
|
INSERT INTO "User" (email, name, display_name, password)
|
||||||
|
WITH RECURSIVE
|
||||||
|
cnt(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT n+1 FROM cnt WHERE n < 100
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
'user' || n || '@example.com',
|
||||||
|
'user_' || n,
|
||||||
|
'User ' || n,
|
||||||
|
'$2b$10$EpjVIByL7WqitL96.t65he1usN8L6j0r6n/1q7R8U6O1Y2v6y/L2.' -- password123
|
||||||
|
FROM cnt;
|
||||||
|
|
||||||
|
-- 2. Generate 700 Parent Posts
|
||||||
|
-- Distributed across the 100 users
|
||||||
|
INSERT INTO "Post" (title, content, userId, createdAt)
|
||||||
|
WITH RECURSIVE
|
||||||
|
cnt(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT n+1 FROM cnt WHERE n < 700
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
'Post Title #' || n,
|
||||||
|
'This is the body content for sample post number ' || n || '. It contains some placeholder text for testing purposes.',
|
||||||
|
(ABS(RANDOM()) % 100) + 1, -- Random userId between 1 and 100
|
||||||
|
datetime('now', '-' || (ABS(RANDOM()) % 365) || ' days') -- Random date in the last year
|
||||||
|
FROM cnt;
|
||||||
|
|
||||||
|
-- 3. Generate 200 Replies
|
||||||
|
-- Linked to random parent posts (IDs 1-700)
|
||||||
|
INSERT INTO "Post" (title, content, userId, parentId, createdAt)
|
||||||
|
WITH RECURSIVE
|
||||||
|
cnt(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT n+1 FROM cnt WHERE n < 200
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
'Reply #' || n,
|
||||||
|
'I am replying to an existing post to test the self-relation logic.',
|
||||||
|
(ABS(RANDOM()) % 100) + 1, -- Random author
|
||||||
|
(ABS(RANDOM()) % 700) + 1, -- Random parent post ID
|
||||||
|
datetime('now', '-' || (ABS(RANDOM()) % 30) || ' days') -- Random date in last 30 days
|
||||||
|
FROM cnt;
|
||||||
15
shell.nix
Normal file
15
shell.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
prisma_7
|
||||||
|
pnpm_10
|
||||||
|
nodejs_25
|
||||||
|
vscode-langservers-extracted
|
||||||
|
typescript
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
export PRISMA_QUERY_ENGINE_LIBRARY=${pkgs.prisma-engines}/lib/libquery_engine.node
|
||||||
|
export PRISMA_QUERY_ENGINE_BINARY=${pkgs.prisma-engines}/bin/query-engine
|
||||||
|
export PRISMA_SCHEMA_ENGINE_BINARY=${pkgs.prisma-engines}/bin/schema-engine
|
||||||
|
'';
|
||||||
|
}
|
||||||
49
shell.nix.bak
Normal file
49
shell.nix.bak
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
(pkgs.buildFHSEnv {
|
||||||
|
name = "prisma-v7-shell";
|
||||||
|
|
||||||
|
targetPkgs = pkgs: (with pkgs; [
|
||||||
|
nodejs
|
||||||
|
pnpm
|
||||||
|
openssl
|
||||||
|
zlib
|
||||||
|
]);
|
||||||
|
|
||||||
|
runScript = "bash";
|
||||||
|
|
||||||
|
profile = ''
|
||||||
|
# 1. Define the correct version and commit for Prisma 7.2.0
|
||||||
|
PRISMA_VERSION="7.2.0"
|
||||||
|
COMMIT="0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
|
||||||
|
# 2. Setup cache directory
|
||||||
|
CACHE_DIR="$HOME/.cache/prisma-nix-fix/$PRISMA_VERSION"
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
# 3. Only download schema-engine (REQUIRED for migrations)
|
||||||
|
# Prisma 7 does not use the Rust query-engine by default, so we skip it to avoid 404s.
|
||||||
|
if [ ! -f "$CACHE_DIR/schema-engine" ]; then
|
||||||
|
echo ">> NixOS Fix: Downloading schema-engine for Prisma $PRISMA_VERSION..."
|
||||||
|
# Use -f to fail silently if the file is missing (though it shouldn't be for schema-engine)
|
||||||
|
curl -fL "https://binaries.prisma.sh/all_commits/$COMMIT/debian-openssl-3.0.x/schema-engine.gz" | gunzip > "$CACHE_DIR/schema-engine"
|
||||||
|
chmod +x "$CACHE_DIR/schema-engine"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Export the specific variable for the Schema Engine
|
||||||
|
export PRISMA_SCHEMA_ENGINE_BINARY="$CACHE_DIR/schema-engine"
|
||||||
|
|
||||||
|
# 5. UNSET the query engine variables.
|
||||||
|
# Prisma 7 will fall back to its internal TypeScript engine (which works fine in FHS).
|
||||||
|
# If we leave these set to broken files (from the 404s), Prisma crashes.
|
||||||
|
unset PRISMA_QUERY_ENGINE_BINARY
|
||||||
|
unset PRISMA_QUERY_ENGINE_LIBRARY
|
||||||
|
unset PRISMA_FMT_BINARY
|
||||||
|
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
echo " Prisma 7.x Shell (NixOS)"
|
||||||
|
echo " - Schema Engine: Pinned to Debian OpenSSL 3.0.x"
|
||||||
|
echo " - Query Engine: Using Prisma Default (TypeScript)"
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
'';
|
||||||
|
}).env
|
||||||
212
src/index.ts
Normal file
212
src/index.ts
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
// src/index.ts
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { PrismaD1 } from '@prisma/adapter-d1';
|
||||||
|
import { Bindings, Variables, UserBody, LoginBody, PostCreationBody } from './types';
|
||||||
|
import { genSalt, hash, compare } from "bcrypt-ts";
|
||||||
|
import { sign_jwt } from './util/jwt'
|
||||||
|
import { authMiddleware } from './middleware/auth';
|
||||||
|
import { JsonObject } from '@prisma/client/runtime/client';
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
import { userCreationSchema, userLoginSchema, postCreationSchema } from './middleware/schemas';
|
||||||
|
import { cors } from 'hono/cors';
|
||||||
|
import { setCookie } from 'hono/cookie';
|
||||||
|
|
||||||
|
|
||||||
|
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
|
||||||
|
|
||||||
|
app.use('*', async (c, next) => {
|
||||||
|
const adapter = new PrismaD1(c.env.DB);
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
c.set('prisma', prisma);
|
||||||
|
await next();
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use('*', cors({
|
||||||
|
origin: 'http://localhost:5173',
|
||||||
|
allowHeaders: ['Content-Type', 'Authorization'], // Specify the headers your clients might send
|
||||||
|
allowMethods: ['POST', 'GET', 'OPTIONS', 'DELETE', 'PATCH', 'PUT'], // Specify the methods you use
|
||||||
|
maxAge: 600, // Optional: cache preflight response for 10 minutes
|
||||||
|
credentials: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.html("<h1>Hello!</h1>")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/users/create', zValidator('json', userCreationSchema), async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const body = await c.req.json<UserBody>();
|
||||||
|
const salt = await genSalt(10);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: body.email,
|
||||||
|
name: body.name,
|
||||||
|
display_name: body.display_name,
|
||||||
|
password: await hash(body.password, salt),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const jwt = await sign_jwt({ id: user.id });
|
||||||
|
setCookie(c, 'auth_token', jwt, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'Lax',
|
||||||
|
path: '/',
|
||||||
|
maxAge: 60 * 60 * 24 * 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json({ err: null, result: user })
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/users/login', zValidator('json', userLoginSchema), async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const body = await c.req.json<LoginBody>();
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email: body.email },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) return c.body("404 Not Found\n");
|
||||||
|
|
||||||
|
const result = await compare(body.password, user.password);
|
||||||
|
if (result) {
|
||||||
|
const jwt = await sign_jwt({ id: user.id });
|
||||||
|
setCookie(c, 'auth_token', jwt, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'Lax',
|
||||||
|
path: '/',
|
||||||
|
maxAge: 60 * 60 * 24 * 7,
|
||||||
|
});
|
||||||
|
return c.json({ err: null, result: user })
|
||||||
|
} else {
|
||||||
|
c.status(403);
|
||||||
|
return c.json({ err: 403 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/posts/add', zValidator('json', postCreationSchema), authMiddleware, async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const userId = c.get('userId');
|
||||||
|
const body = await c.req.json<PostCreationBody>();
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return c.text("403 forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await prisma.post.create({
|
||||||
|
data: {
|
||||||
|
title: body.title,
|
||||||
|
content: body.content,
|
||||||
|
author: { connect: { id: user.id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(post)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/posts/:id/reply', zValidator('json', postCreationSchema), authMiddleware, async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const userId = c.get('userId');
|
||||||
|
const body = await c.req.json<PostCreationBody>();
|
||||||
|
|
||||||
|
// auth check
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return c.text("403 forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentIdParameter = c.req.param("id");
|
||||||
|
if (isNaN(Number(parentIdParameter))) {
|
||||||
|
c.status(400)
|
||||||
|
return c.text("400 bad request")
|
||||||
|
}
|
||||||
|
const parentId = Number(parentIdParameter)
|
||||||
|
|
||||||
|
const parent_post = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parentId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parent_post) {
|
||||||
|
return c.text("404 not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually make the post
|
||||||
|
|
||||||
|
const post = await prisma.post.create({
|
||||||
|
data: {
|
||||||
|
title: body.title,
|
||||||
|
content: body.content,
|
||||||
|
author: { connect: { id: user.id } },
|
||||||
|
parent: { connect: { id: parentId } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(post)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Helper to generate nested includes with limits
|
||||||
|
function getNestedReplies(depth: number, breadth: number): JsonObject {
|
||||||
|
if (depth === 0) return {};
|
||||||
|
return {
|
||||||
|
include: {
|
||||||
|
author: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
display_name: true,
|
||||||
|
id: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
replies: {
|
||||||
|
take: breadth, // limit breadth
|
||||||
|
orderBy: { createdAt: 'desc' as const },
|
||||||
|
...getNestedReplies(depth - 1, breadth),
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
select: { replies: true } // indicator of if there's more stuff to load
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/posts/:id/view', async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const post_id = c.req.param('id');
|
||||||
|
|
||||||
|
const post = await prisma.post.findMany({
|
||||||
|
where: {
|
||||||
|
id: Number(post_id),
|
||||||
|
},
|
||||||
|
...getNestedReplies(3, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.json(post)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/feed', async (c) => {
|
||||||
|
const prisma = c.get('prisma');
|
||||||
|
const posts = await prisma.post.findMany({
|
||||||
|
where: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc'
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
...getNestedReplies(2, 3)
|
||||||
|
});
|
||||||
|
return c.json(posts);
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
||||||
24
src/middleware/auth.ts
Normal file
24
src/middleware/auth.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// src/middleware/auth.ts
|
||||||
|
import { createMiddleware } from 'hono/factory'
|
||||||
|
import { verify_jwt } from '../util/jwt'
|
||||||
|
import { Bindings, Variables } from '../types'
|
||||||
|
import { getCookie } from 'hono/cookie';
|
||||||
|
|
||||||
|
export const authMiddleware = createMiddleware<{
|
||||||
|
Bindings: Bindings,
|
||||||
|
Variables: Variables
|
||||||
|
}>(async (c, next) => {
|
||||||
|
const auth = getCookie(c, 'auth_token');
|
||||||
|
|
||||||
|
if (!auth) {
|
||||||
|
return c.json({ err: 'Unauthorized' }, 401)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = await verify_jwt(auth)
|
||||||
|
c.set('userId', payload.id)
|
||||||
|
await next()
|
||||||
|
} catch {
|
||||||
|
return c.text("Invalid Token", 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
16
src/middleware/prisma.ts
Normal file
16
src/middleware/prisma.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { createMiddleware } from 'hono/factory'
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
import { PrismaD1 } from '@prisma/adapter-d1'
|
||||||
|
import { Bindings, Variables } from '../types'
|
||||||
|
|
||||||
|
export const prismaMiddleware = createMiddleware<{
|
||||||
|
Bindings: Bindings,
|
||||||
|
Variables: Variables
|
||||||
|
}>(async (c, next) => {
|
||||||
|
const adapter = new PrismaD1(c.env.DB)
|
||||||
|
const prisma = new PrismaClient({ adapter })
|
||||||
|
|
||||||
|
c.set('prisma', prisma)
|
||||||
|
|
||||||
|
await next()
|
||||||
|
})
|
||||||
21
src/middleware/schemas.ts
Normal file
21
src/middleware/schemas.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// src/middleware/schemas.ts
|
||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
export const userCreationSchema = z.object({
|
||||||
|
email: z.email().max(255),
|
||||||
|
name: z.string().min(3).max(32),
|
||||||
|
display_name: z.string().min(3).max(32),
|
||||||
|
password: z.string().min(8).max(255),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const userLoginSchema = z.object({
|
||||||
|
email: z.email().max(255),
|
||||||
|
password: z.string().min(8).max(255)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const postCreationSchema = z.object({
|
||||||
|
title: z.string().min(1).max(128),
|
||||||
|
content: z.string().min(1).max(2147483647)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
30
src/types.ts
Normal file
30
src/types.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// src/types.ts
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
export interface Bindings {
|
||||||
|
// @ts-expect-error Defined by God Himself. (idk ask Cloudflare)
|
||||||
|
DB: D1Database;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Variables {
|
||||||
|
prisma: PrismaClient;
|
||||||
|
userId: number;
|
||||||
|
postId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserBody {
|
||||||
|
email: string
|
||||||
|
name: string
|
||||||
|
display_name: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginBody {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostCreationBody {
|
||||||
|
title: string,
|
||||||
|
content: string,
|
||||||
|
}
|
||||||
28
src/util/jwt.ts
Normal file
28
src/util/jwt.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import * as jose from 'jose'
|
||||||
|
|
||||||
|
const issuer = "Dingaling Industries LLC";
|
||||||
|
// @ts-expect-error Defined by CF.
|
||||||
|
const secret = new TextEncoder().encode("heilstalin");
|
||||||
|
|
||||||
|
interface PayloadBody {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sign_jwt(payload: jose.JWTPayload) {
|
||||||
|
|
||||||
|
const alg = 'HS256';
|
||||||
|
|
||||||
|
const jwt = await new jose.SignJWT(payload)
|
||||||
|
.setIssuedAt()
|
||||||
|
.setIssuer(issuer)
|
||||||
|
.setExpirationTime('2w')
|
||||||
|
.setProtectedHeader({ alg })
|
||||||
|
.sign(secret)
|
||||||
|
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verify_jwt(jwt: string) {
|
||||||
|
const { payload } = await jose.jwtVerify(jwt, secret);
|
||||||
|
return payload as unknown as PayloadBody;
|
||||||
|
}
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx"
|
||||||
|
},
|
||||||
|
}
|
||||||
16
wrangler.jsonc
Normal file
16
wrangler.jsonc
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "anum-backend",
|
||||||
|
"compatibility_date": "2025-08-03",
|
||||||
|
"compatibility_flags": [
|
||||||
|
"nodejs_compat"
|
||||||
|
],
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"d1_databases": [
|
||||||
|
{
|
||||||
|
"binding": "DB",
|
||||||
|
"database_name": "anum",
|
||||||
|
"database_id": "to be filled when i get remote cloudflare workers up"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue