aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flake.nix28
-rw-r--r--scripts/gen-invite.js34
-rw-r--r--src/db.js17
-rw-r--r--src/invite.js8
-rw-r--r--src/mixins/post.pug61
-rw-r--r--src/mixins/postUtils.pug39
-rw-r--r--src/routes/index.js24
-rw-r--r--src/views/comments.pug28
8 files changed, 103 insertions, 136 deletions
diff --git a/flake.nix b/flake.nix
index a3db456..4f6b0dc 100644
--- a/flake.nix
+++ b/flake.nix
@@ -42,32 +42,6 @@
42 outputHashAlgo = "sha256"; 42 outputHashAlgo = "sha256";
43 outputHashMode = "recursive"; 43 outputHashMode = "recursive";
44 }; 44 };
45 readit-gen-invite = with final;
46 stdenv.mkDerivation {
47 pname = "readit-gen-invite";
48 version = "0.0.1";
49 src = ./scripts;
50 nativeBuildInputs = [makeBinaryWrapper];
51 buildInputs = [bun];
52
53 buildPhase = ''
54 runHook preBuild
55 runHook postBuild
56 '';
57
58 dontFixup = true;
59
60 installPhase = ''
61 runHook preInstall
62
63 mkdir -p $out/bin
64 cp -R ./* $out
65
66 makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \
67 --prefix PATH : ${lib.makeBinPath [bun]} \
68 --add-flags "run --prefer-offline --no-install $out/gen-invite.js"
69 '';
70 };
71 readit = with final; 45 readit = with final;
72 stdenv.mkDerivation { 46 stdenv.mkDerivation {
73 pname = "readit"; 47 pname = "readit";
@@ -110,7 +84,7 @@
110 }); 84 });
111 85
112 packages = forAllSystems (system: { 86 packages = forAllSystems (system: {
113 inherit (nixpkgsFor."${system}") readit readit-gen-invite node_modules; 87 inherit (nixpkgsFor."${system}") readit node_modules;
114 }); 88 });
115 89
116 defaultPackage = forAllSystems (system: nixpkgsFor."${system}".readit); 90 defaultPackage = forAllSystems (system: nixpkgsFor."${system}".readit);
diff --git a/scripts/gen-invite.js b/scripts/gen-invite.js
deleted file mode 100644
index 4c0cbee..0000000
--- a/scripts/gen-invite.js
+++ /dev/null
@@ -1,34 +0,0 @@
1import { Database } from "bun:sqlite";
2
3const command = process.argv[2];
4
5const dbPath = process.argv[3] ? process.argv[3] : "readit.db";
6const db = new Database(dbPath, {
7 strict: true,
8});
9
10db.run(`
11 CREATE TABLE IF NOT EXISTS invites (
12 id INTEGER PRIMARY KEY AUTOINCREMENT,
13 token TEXT NOT NULL,
14 createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
15 usedAt TIMESTAMP
16 )
17`);
18
19if (command === "create") {
20 createInvite();
21} else {
22 console.log("requires an arg");
23}
24
25function generateInviteToken() {
26 const hasher = new Bun.CryptoHasher("sha256", "super-secret-invite-key");
27 return hasher.update(Math.random().toString()).digest("hex").slice(0, 10);
28}
29
30function createInvite() {
31 const token = generateInviteToken();
32 db.run("INSERT INTO invites (token) VALUES ($token)", { token });
33 console.log(`Invite token created: ${token}`);
34}
diff --git a/src/db.js b/src/db.js
index 747168a..c1fecac 100644
--- a/src/db.js
+++ b/src/db.js
@@ -15,16 +15,16 @@ function runMigration(name, migrationFn) {
15} 15}
16 16
17// users table 17// users table
18db.query(` 18db.run(`
19 CREATE TABLE IF NOT EXISTS users ( 19 CREATE TABLE IF NOT EXISTS users (
20 id INTEGER PRIMARY KEY AUTOINCREMENT, 20 id INTEGER PRIMARY KEY AUTOINCREMENT,
21 username TEXT UNIQUE, 21 username TEXT UNIQUE,
22 password_hash TEXT 22 password_hash TEXT
23 ) 23 )
24`).run(); 24`);
25 25
26// subs table 26// subs table
27db.query(` 27db.run(`
28 CREATE TABLE IF NOT EXISTS subscriptions ( 28 CREATE TABLE IF NOT EXISTS subscriptions (
29 id INTEGER PRIMARY KEY AUTOINCREMENT, 29 id INTEGER PRIMARY KEY AUTOINCREMENT,
30 user_id INTEGER, 30 user_id INTEGER,
@@ -32,7 +32,16 @@ db.query(`
32 FOREIGN KEY(user_id) REFERENCES users(id), 32 FOREIGN KEY(user_id) REFERENCES users(id),
33 UNIQUE(user_id, subreddit) 33 UNIQUE(user_id, subreddit)
34 ) 34 )
35`).run(); 35`);
36
37db.run(`
38 CREATE TABLE IF NOT EXISTS invites (
39 id INTEGER PRIMARY KEY AUTOINCREMENT,
40 token TEXT NOT NULL,
41 createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
42 usedAt TIMESTAMP
43 )
44`);
36 45
37// migrations table 46// migrations table
38db.query(` 47db.query(`
diff --git a/src/invite.js b/src/invite.js
index 7e357ac..f0bc9b9 100644
--- a/src/invite.js
+++ b/src/invite.js
@@ -1,6 +1,14 @@
1const { db } = require("./db"); 1const { db } = require("./db");
2 2
3const validateInviteToken = async (req, res, next) => { 3const validateInviteToken = async (req, res, next) => {
4 const isFirstUser = db.query("SELECT 1 FROM users LIMIT 1").get() === null;
5
6 if (isFirstUser) {
7 req.isFirstUser = true;
8 next();
9 return;
10 }
11
4 const token = req.query.token; 12 const token = req.query.token;
5 13
6 if (!token) { 14 if (!token) {
diff --git a/src/mixins/post.pug b/src/mixins/post.pug
index 172da32..487f366 100644
--- a/src/mixins/post.pug
+++ b/src/mixins/post.pug
@@ -1,4 +1,5 @@
1include ../utils 1include ../utils
2include postUtils
2mixin post(p) 3mixin post(p)
3 article.post 4 article.post
4 div.post-container 5 div.post-container
@@ -20,62 +21,42 @@ mixin post(p)
20 |  ·  21 |  · 
21 a(href=`/comments/${p.id}`) #{fmtnum (p.num_comments)} ↩ 22 a(href=`/comments/${p.id}`) #{fmtnum (p.num_comments)} ↩
22 div.media-preview 23 div.media-preview
23 if p.is_gallery && p.is_gallery == true 24 if isPostGallery(p)
24 if p.gallery_data 25 - var item = postGalleryItems(p)[0]
25 if p.gallery_data.items 26 img(src=item.url onclick=`toggleDetails('${p.id}')`)
26 - var item = p.gallery_data.items[0] 27 else if isPostImage(p)
27 - var url = `https://i.redd.it/${item.media_id}.jpg`
28 img(src=url onclick=`toggleDetails('${p.id}')`)
29 else if p.post_hint == "image" && p.thumbnail && p.thumbnail != "self" && p.thumbnail != "default"
30 img(src=p.thumbnail onclick=`toggleDetails('${p.id}')`) 28 img(src=p.thumbnail onclick=`toggleDetails('${p.id}')`)
31 else if p.post_hint == "hosted:video" 29 else if isPostVideo(p)
32 - var url = p.secure_media.reddit_video.scrubber_media_url 30 - var url = p.secure_media.reddit_video.scrubber_media_url
33 video(src=url data-dashjs-player width='100px' height='100px' onclick=`toggleDetails('${p.id}')`) 31 video(src=url data-dashjs-player width='100px' height='100px' onclick=`toggleDetails('${p.id}')`)
34 else if p.post_hint == "link" 32 else if isPostLink(p)
35 a(href=p.url) 33 a(href=p.url)
36 | ↗ 34 | ↗
37 35
38 if p.is_gallery && p.is_gallery == true 36 if isPostGallery(p)
39 if p.gallery_data 37 details(id=`${p.id}`)
40 if p.gallery_data.items 38 summary.expand-post expand gallery
41 details(id=`${p.id}`) 39 div.gallery
42 summary.expand-post expand gallery 40 each item in postGalleryItems(p)
43 div.gallery 41 div.gallery-item
44 - var total = p.gallery_data.items.length 42 div.gallery-item-idx
45 - var idx = 0 43 | #{`${item.idx}/${item.total}`}
46 - var metadata = p.media_metadata 44 a(href=`/media/${item.url}`)
47 - 45 img(src=item.url loading="lazy")
48 var img_ext = (id) => { 46 button(onclick=`toggleDetails('${p.id}')`) close
49 if (metadata[id].status == 'valid') { 47 else if isPostImage(p)
50 return stripPrefix(metadata[id].m, "image/");
51 } else {
52 // dosent matter
53 return 'jpg';
54 }
55 }
56 each item in p.gallery_data.items
57 - var id = item.media_id
58 - var ext = img_ext(item.media_id)
59 - var url = `https://i.redd.it/${id}.${ext}`
60 div.gallery-item
61 a(href=`/media/${url}`)
62 img(src=url loading="lazy")
63 div.gallery-item-idx
64 | #{`${++idx}/${total}`}
65 button(onclick=`toggleDetails('${p.id}')`) close
66 if p.post_hint == "image" && p.thumbnail && p.thumbnail != "self" && p.thumbnail != "default"
67 details(id=`${p.id}`) 48 details(id=`${p.id}`)
68 summary.expand-post expand image 49 summary.expand-post expand image
69 a(href=`/media/${p.url}`) 50 a(href=`/media/${p.url}`)
70 img(src=p.url loading="lazy").post-media 51 img(src=p.url loading="lazy").post-media
71 button(onclick=`toggleDetails('${p.id}')`) close 52 button(onclick=`toggleDetails('${p.id}')`) close
72 else if p.post_hint == "hosted:video" 53 else if isPostVideo(p)
73 details(id=`${p.id}`) 54 details(id=`${p.id}`)
74 summary.expand-post expand video 55 summary.expand-post expand video
75 - var url = p.secure_media.reddit_video.dash_url 56 - var url = p.secure_media.reddit_video.dash_url
76 video(src=url controls data-dashjs-player loading="lazy").post-media 57 video(src=url controls data-dashjs-player loading="lazy").post-media
77 button(onclick=`toggleDetails('${p.id}')`) close 58 button(onclick=`toggleDetails('${p.id}')`) close
78 else if p.post_hint == "link" 59 else if isPostLink(p)
79 details(id=`${p.id}`) 60 details(id=`${p.id}`)
80 summary.expand-post expand link 61 summary.expand-post expand link
81 a(href=`${p.url}`) 62 a(href=`${p.url}`)
diff --git a/src/mixins/postUtils.pug b/src/mixins/postUtils.pug
new file mode 100644
index 0000000..4f480b6
--- /dev/null
+++ b/src/mixins/postUtils.pug
@@ -0,0 +1,39 @@
1-
2 function isPostGallery(p) {
3 return (p.is_gallery && p.is_gallery == true);
4 }
5-
6 function isPostImage(p) {
7 return (p.post_hint == "image" && p.thumbnail && p.thumbnail != "self" && p.thumbnail != "default");
8 }
9-
10 function isPostVideo(p) {
11 return (p.post_hint == "hosted:video");
12 }
13-
14 function isPostLink(p) {
15 return (p.post_hint == "link");
16 }
17-
18 function imgExt(p, id) {
19 var metadata = p.media_metadata;
20 if (metadata[id].status == 'valid') {
21 return stripPrefix(metadata[id].m, "image/");
22 } else {
23 // dosent matter
24 return 'jpg';
25 }
26 }
27-
28 function postGalleryItems(p) {
29 if (p.gallery_data && p.gallery_data.items) {
30 return p.gallery_data.items.map((item, idx) => ({
31 id: item.media_id,
32 url: `https://i.redd.it/${item.media_id}.${imgExt(p, item.media_id)}`,
33 total: p.gallery_data.items.length,
34 idx: idx+1,
35 }));
36 } else {
37 return null;
38 }
39 }
diff --git a/src/routes/index.js b/src/routes/index.js
index 9a415be..e585d3d 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -136,15 +136,6 @@ router.get("/create-invite", authenticateAdmin, async (req, res) => {
136 } 136 }
137 137
138 try { 138 try {
139 db.run(`
140 CREATE TABLE IF NOT EXISTS invites (
141 id INTEGER PRIMARY KEY AUTOINCREMENT,
142 token TEXT NOT NULL,
143 createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
144 usedAt TIMESTAMP
145 )
146 `);
147
148 createInvite(); 139 createInvite();
149 return res.redirect("/dashboard"); 140 return res.redirect("/dashboard");
150 } catch (err) { 141 } catch (err) {
@@ -201,19 +192,22 @@ router.post("/register", validateInviteToken, async (req, res) => {
201 try { 192 try {
202 const hashedPassword = await Bun.password.hash(password); 193 const hashedPassword = await Bun.password.hash(password);
203 194
204 db.query( 195 if (!req.isFirstUser) {
205 "UPDATE invites SET usedAt = CURRENT_TIMESTAMP WHERE id = $id", 196 db.query(
206 ).run({ 197 "UPDATE invites SET usedAt = CURRENT_TIMESTAMP WHERE id = $id",
207 id: req.invite.id, 198 ).run({
208 }); 199 id: req.invite.id,
200 });
201 }
209 202
210 const insertedRecord = db 203 const insertedRecord = db
211 .query( 204 .query(
212 "INSERT INTO users (username, password_hash) VALUES ($username, $hashedPassword)", 205 "INSERT INTO users (username, password_hash, isAdmin) VALUES ($username, $hashedPassword, $isAdmin)",
213 ) 206 )
214 .run({ 207 .run({
215 username, 208 username,
216 hashedPassword, 209 hashedPassword,
210 isAdmin: req.isFirstUser ? 1 : 0,
217 }); 211 });
218 const id = insertedRecord.lastInsertRowid; 212 const id = insertedRecord.lastInsertRowid;
219 const token = jwt.sign({ username, id }, JWT_KEY, { expiresIn: "5d" }); 213 const token = jwt.sign({ username, id }, JWT_KEY, { expiresIn: "5d" });
diff --git a/src/views/comments.pug b/src/views/comments.pug
index 541a7bd..e9bd332 100644
--- a/src/views/comments.pug
+++ b/src/views/comments.pug
@@ -1,6 +1,7 @@
1include ../mixins/comment 1include ../mixins/comment
2include ../mixins/header 2include ../mixins/header
3include ../mixins/head 3include ../mixins/head
4include ../mixins/postUtils
4include ../utils 5include ../utils
5 6
6- var post = data.post 7- var post = data.post
@@ -33,25 +34,20 @@ html
33 h2.post-title 34 h2.post-title
34 != post.title 35 != post.title
35 36
36 if post.is_gallery && post.is_gallery == true 37 if isPostGallery(post)
37 if post.gallery_data 38 div.gallery
38 if post.gallery_data.items 39 each item in postGalleryItems(post)
39 div.gallery 40 div.gallery-item
40 - var total = post.gallery_data.items.length 41 div.gallery-item-idx
41 - var idx = 0 42 | #{`${item.idx}/${item.total}`}
42 each item in post.gallery_data.items 43 a(href=`/media/${item.url}`)
43 - var url = `https://i.redd.it/${item.media_id}.jpg` 44 img(src=item.url loading="lazy")
44 div.gallery-item 45 else if isPostImage(post)
45 div.gallery-item-idx
46 | #{`${++idx}/${total}`}
47 a(href=`/media/${url}`)
48 img(src=url loading="lazy")
49 else if post.post_hint == "image" && post.thumbnail && post.thumbnail != "self" && post.thumbnail != "default"
50 img(src=post.url).post-media 46 img(src=post.url).post-media
51 else if post.post_hint == 'hosted:video' 47 else if isPostVideo(post)
52 - var url = post.secure_media.reddit_video.dash_url 48 - var url = post.secure_media.reddit_video.dash_url
53 video(controls data-dashjs-player src=`${url}`).post-media 49 video(controls data-dashjs-player src=`${url}`).post-media
54 else if post.post_hint == "link" 50 else if isPostLink(post)
55 a(href=post.url) 51 a(href=post.url)
56 | #{post.url} 52 | #{post.url}
57 53