diff --git a/.TODO b/.TODO
deleted file mode 100644
index e2ff786..0000000
--- a/.TODO
+++ /dev/null
@@ -1,4 +0,0 @@
-[ ] An audit log
-[ ] Invite code pruning
-[ ] Make the journal work (i dont really remember what was left out though)
-[ ] Write a 404 page
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..8416870
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,8 @@
+- [ ] An audit log
+- [ ] Invite code pruning
+- [ ] Make the journal work (i dont really remember what was left out though)
+- [x] Write a 404 page
+- [ ] Make better login pages
+- [ ] You do realize using toLocaleString on the server only makes it use your locale right
+- [ ] Make recent updates also account for new journal entries
+- [ ] View all previous moods and journal entries
diff --git a/db/schema.ts b/db/schema.ts
index 5cfb5b5..ad9ef68 100644
--- a/db/schema.ts
+++ b/db/schema.ts
@@ -32,6 +32,7 @@ export const journalEntries = pgTable("journal_entries", {
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
moodChange: integer("mood-change").default(0).notNull(),
+ title: varchar({ length: 64 }).default("").notNull(),
entry: varchar({ length: 4096 }).default("").notNull(),
visibility: integer().default(1).notNull(),
date: timestamp().notNull()
diff --git a/drizzle/0001_messy_fallen_one.sql b/drizzle/0001_messy_fallen_one.sql
new file mode 100644
index 0000000..51f99ea
--- /dev/null
+++ b/drizzle/0001_messy_fallen_one.sql
@@ -0,0 +1 @@
+ALTER TABLE "journal_entries" ADD COLUMN "title" varchar(128) DEFAULT '' NOT NULL;
\ No newline at end of file
diff --git a/drizzle/0002_special_ben_parker.sql b/drizzle/0002_special_ben_parker.sql
new file mode 100644
index 0000000..ee22776
--- /dev/null
+++ b/drizzle/0002_special_ben_parker.sql
@@ -0,0 +1 @@
+ALTER TABLE "journal_entries" ALTER COLUMN "title" SET DATA TYPE varchar(64);
\ No newline at end of file
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..e4d9fb8
--- /dev/null
+++ b/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,415 @@
+{
+ "id": "a5d70822-0a93-4193-9115-c41b2ee9f5d8",
+ "prevId": "05baee8f-73c9-4001-b69d-ba7c46dadf5c",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.follows": {
+ "name": "follows",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "follower_id": {
+ "name": "follower_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "follows_user_id_users_id_fk": {
+ "name": "follows_user_id_users_id_fk",
+ "tableFrom": "follows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "follows_follower_id_users_id_fk": {
+ "name": "follows_follower_id_users_id_fk",
+ "tableFrom": "follows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "follower_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "follows_user_id_follower_id_pk": {
+ "name": "follows_user_id_follower_id_pk",
+ "columns": [
+ "user_id",
+ "follower_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invite_codes": {
+ "name": "invite_codes",
+ "schema": "",
+ "columns": {
+ "token": {
+ "name": "token",
+ "type": "varchar(22)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted": {
+ "name": "granted",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'1970-01-01 00:00:00.000'"
+ },
+ "confers": {
+ "name": "confers",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invite_codes_user_id_users_id_fk": {
+ "name": "invite_codes_user_id_users_id_fk",
+ "tableFrom": "invite_codes",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.journal_entries": {
+ "name": "journal_entries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "journal_entries_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mood-change": {
+ "name": "mood-change",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "entry": {
+ "name": "entry",
+ "type": "varchar(4096)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "date": {
+ "name": "date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "journal_entries_user_users_id_fk": {
+ "name": "journal_entries_user_users_id_fk",
+ "tableFrom": "journal_entries",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.profiles": {
+ "name": "profiles",
+ "schema": "",
+ "columns": {
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "varchar(1024)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "website": {
+ "name": "website",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "profiles_user_users_id_fk": {
+ "name": "profiles_user_users_id_fk",
+ "tableFrom": "profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.updates": {
+ "name": "updates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "updates_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mood": {
+ "name": "mood",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "date": {
+ "name": "date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "updates_user_users_id_fk": {
+ "name": "updates_user_users_id_fk",
+ "tableFrom": "updates",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "users_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(26)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pass": {
+ "name": "pass",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registered": {
+ "name": "registered",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ },
+ "users_name_unique": {
+ "name": "users_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json
new file mode 100644
index 0000000..b98b82e
--- /dev/null
+++ b/drizzle/meta/0002_snapshot.json
@@ -0,0 +1,415 @@
+{
+ "id": "f8e1fcdc-dfa2-41cd-886a-5f29a1c091df",
+ "prevId": "a5d70822-0a93-4193-9115-c41b2ee9f5d8",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.follows": {
+ "name": "follows",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "follower_id": {
+ "name": "follower_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "follows_user_id_users_id_fk": {
+ "name": "follows_user_id_users_id_fk",
+ "tableFrom": "follows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "follows_follower_id_users_id_fk": {
+ "name": "follows_follower_id_users_id_fk",
+ "tableFrom": "follows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "follower_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "follows_user_id_follower_id_pk": {
+ "name": "follows_user_id_follower_id_pk",
+ "columns": [
+ "user_id",
+ "follower_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invite_codes": {
+ "name": "invite_codes",
+ "schema": "",
+ "columns": {
+ "token": {
+ "name": "token",
+ "type": "varchar(22)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted": {
+ "name": "granted",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'1970-01-01 00:00:00.000'"
+ },
+ "confers": {
+ "name": "confers",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invite_codes_user_id_users_id_fk": {
+ "name": "invite_codes_user_id_users_id_fk",
+ "tableFrom": "invite_codes",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.journal_entries": {
+ "name": "journal_entries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "journal_entries_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mood-change": {
+ "name": "mood-change",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "entry": {
+ "name": "entry",
+ "type": "varchar(4096)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "date": {
+ "name": "date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "journal_entries_user_users_id_fk": {
+ "name": "journal_entries_user_users_id_fk",
+ "tableFrom": "journal_entries",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.profiles": {
+ "name": "profiles",
+ "schema": "",
+ "columns": {
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "varchar(1024)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "website": {
+ "name": "website",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "profiles_user_users_id_fk": {
+ "name": "profiles_user_users_id_fk",
+ "tableFrom": "profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.updates": {
+ "name": "updates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "updates_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "user": {
+ "name": "user",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mood": {
+ "name": "mood",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "date": {
+ "name": "date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "updates_user_users_id_fk": {
+ "name": "updates_user_users_id_fk",
+ "tableFrom": "updates",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "users_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "2147483647",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(26)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pass": {
+ "name": "pass",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registered": {
+ "name": "registered",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ },
+ "users_name_unique": {
+ "name": "users_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index c4adb34..a018abb 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -8,6 +8,20 @@
"when": 1733952034836,
"tag": "0000_hard_leader",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1735184859424,
+ "tag": "0001_messy_fallen_one",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1735185254730,
+ "tag": "0002_special_ben_parker",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/main.ts b/main.ts
index 949c21a..4447de6 100644
--- a/main.ts
+++ b/main.ts
@@ -91,12 +91,13 @@ object-src 'none'; base-uri 'none';"
return {
user: e.user,
mood: moods[e.mood],
- date: date.to(dayjs(e.date)),
+ date: e.date,
+ relativeDate: date.to(dayjs(e.date)),
desc: e.desc
};
});
- render(db, "index", "Home", res, req, {
+ render(db, "index", "home", res, req, {
users: (await db.select().from(users)).length,
recentUpdates,
feedUpdates
diff --git a/routes/admin.ts b/routes/admin.ts
index b1d9f79..5b169e3 100644
--- a/routes/admin.ts
+++ b/routes/admin.ts
@@ -36,7 +36,7 @@ export default function (app: Express, db: NodePgDatabase) {
expiresString: now.to(dayjs(e.expires))
};
});
- render(db, "admin", "Admin Panel", res, req, { codes });
+ render(db, "admin", "admin panel", res, req, { codes });
});
app.post("/codes/delete", async (req, res) => {
diff --git a/routes/login.ts b/routes/login.ts
index 0130c24..22a98e7 100644
--- a/routes/login.ts
+++ b/routes/login.ts
@@ -13,7 +13,7 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/");
return;
}
- render(db, "register", "Sign up", res, req);
+ render(db, "register", "sign up", res, req);
});
app.post("/register", async (req, res) => {
if (req.session["loggedIn"]) {
@@ -49,22 +49,6 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/register");
return;
}
-
- // invite code checking
- const code = (await db.select({ expires: inviteCodes.expires, confers: inviteCodes.confers }).from(inviteCodes).where(eq(inviteCodes.token, req.body.referral)).limit(1))[0];
- if (!code) {
- req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
- res.redirect("/register");
- return;
- }
- const expiration = code.expires.getTime();
- if (expiration > 0 && Date.now() >= expiration) {
- req.flash("error", "That code is expired.");
- res.redirect("/register");
- return;
- }
- // we're verified now so get that dumb fucker out of the database
- await db.delete(inviteCodes).where(eq(inviteCodes.token, req.body.referral));
// field conflicts
if (
@@ -84,6 +68,22 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/register");
return;
}
+
+ // invite code checking
+ const code = (await db.select({ expires: inviteCodes.expires, confers: inviteCodes.confers }).from(inviteCodes).where(eq(inviteCodes.token, req.body.referral)).limit(1))[0];
+ if (!code) {
+ req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
+ res.redirect("/register");
+ return;
+ }
+ const expiration = code.expires.getTime();
+ if (expiration > 0 && Date.now() >= expiration) {
+ req.flash("error", "That code is expired.");
+ res.redirect("/register");
+ return;
+ }
+ // we're verified now so get that dumb fucker out of the database
+ await db.delete(inviteCodes).where(eq(inviteCodes.token, req.body.referral));
const hash = await bcrypt.hash(req.body.pass, 10);
const { uid } = (
@@ -117,7 +117,7 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/");
return;
}
- render(db, "login", "Log in", res, req);
+ render(db, "login", "log in", res, req);
});
app.post("/login", async (req, res) => {
if (req.session["loggedIn"]) {
diff --git a/routes/updates.ts b/routes/updates.ts
index 5fadc6c..9334c9e 100644
--- a/routes/updates.ts
+++ b/routes/updates.ts
@@ -1,219 +1,239 @@
import { NodePgDatabase } from "drizzle-orm/node-postgres";
import { Express } from "express";
import {
- follows,
- inviteCodes,
- journalEntries,
- profiles,
- updates,
- users
+ follows,
+ inviteCodes,
+ journalEntries,
+ profiles,
+ updates,
+ users
} from "../db/schema.js";
import { and, count, desc, eq, sql } from "drizzle-orm";
import dayjs from "dayjs";
-import { getMoods, render } from "./util.js";
+import { getMoods, render, render404 } from "./util.js";
export default async function (app: Express, db: NodePgDatabase) {
- const { moods, moodsSorted } = await getMoods();
+ const { moods, moodsSorted } = await getMoods();
- // DASHBOARD
- app.get("/dashboard", async (req, res) => {
- if (!req.session["loggedIn"]) {
- res.redirect("/login");
- return;
- }
- const user = (
- await db
- .select({
- name: users.name,
- bio: profiles.bio,
- website: profiles.website //! validate this
- })
- .from(users)
- .where(eq(users.name, req.session["user"]))
- .leftJoin(profiles, eq(users.id, profiles.user))
- )[0];
-
- const now = dayjs();
- const moodHistory = (
- await db
- .select({ mood: updates.mood, date: updates.date })
- .from(updates)
- .where(eq(updates.user, req.session["uid"]))
- .orderBy(desc(updates.date))
- .limit(10)
- ).map((e) => {
- return { mood: moods[e.mood], date: now.to(dayjs(e.date)) };
- });
-
- const recentUpdates = (
- await db
- .select({
- user: users.name,
- mood: updates.mood,
- desc: updates.description,
- date: updates.date
- })
- .from(updates)
- .innerJoin(
- follows,
- and(
- eq(follows.userId, updates.user),
- eq(follows.followerId, req.session["uid"])
- )
- )
- .leftJoin(users, eq(updates.user, users.id))
- .orderBy(desc(updates.date))
- .limit(25)
- ).map((e) => {
- return {
- user: e.user,
- mood: moods[e.mood],
- desc: e.desc,
- date: now.to(dayjs(e.date))
- };
- });
-
- // user invite codes
- const codes = (await db
- .select({ token: inviteCodes.token, expires: inviteCodes.expires })
- .from(inviteCodes)
- .where(eq(inviteCodes.user, req.session["uid"]))).map((e) => {
- return {
- token: e.token,
- expires: now.to(dayjs(e.expires || 0))
+ // DASHBOARD
+ app.get("/dashboard", async (req, res) => {
+ if (!req.session["loggedIn"]) {
+ res.redirect("/login");
+ return;
}
- });
- const { codesUsed } = (
- await db
- .select({ codesUsed: count() })
- .from(inviteCodes)
- .where(
- and(
- eq(inviteCodes.user, req.session["uid"]),
- eq(
- sql`extract(month from granted)`,
- sql`extract(month from current_date)`
- )
- )
- )
- )[0];
+ const user = (
+ await db
+ .select({
+ name: users.name,
+ bio: profiles.bio,
+ website: profiles.website //! validate this
+ })
+ .from(users)
+ .where(eq(users.name, req.session["user"]))
+ .leftJoin(profiles, eq(users.id, profiles.user))
+ )[0];
- render(db, "dashboard", "Dashboard", res, req, {
- user,
- moods,
- moodsSorted,
- moodHistory,
- recentUpdates,
- codes,
- codesUsed,
- feed: []
+ const now = dayjs();
+ const moodHistory = (
+ await db
+ .select({ mood: updates.mood, date: updates.date })
+ .from(updates)
+ .where(eq(updates.user, req.session["uid"]))
+ .orderBy(desc(updates.date))
+ .limit(10)
+ ).map((e) => {
+ return { mood: moods[e.mood], date: now.to(dayjs(e.date)) };
+ });
+
+ const recentUpdates = (
+ await db
+ .select({
+ user: users.name,
+ mood: updates.mood,
+ desc: updates.description,
+ date: updates.date
+ })
+ .from(updates)
+ .innerJoin(
+ follows,
+ and(
+ eq(follows.userId, updates.user),
+ eq(follows.followerId, req.session["uid"])
+ )
+ )
+ .leftJoin(users, eq(updates.user, users.id))
+ .orderBy(desc(updates.date))
+ .limit(25)
+ ).map((e) => {
+ return {
+ user: e.user,
+ mood: moods[e.mood],
+ desc: e.desc,
+ date: e.date,
+ relativeDate: now.to(dayjs(e.date))
+ };
+ });
+
+ // user invite codes
+ const codes = (
+ await db
+ .select({
+ token: inviteCodes.token,
+ expires: inviteCodes.expires
+ })
+ .from(inviteCodes)
+ .where(eq(inviteCodes.user, req.session["uid"]))
+ ).map((e) => {
+ return {
+ token: e.token,
+ expires: now.to(dayjs(e.expires || 0))
+ };
+ });
+ const { codesUsed } = (
+ await db
+ .select({ codesUsed: count() })
+ .from(inviteCodes)
+ .where(
+ and(
+ eq(inviteCodes.user, req.session["uid"]),
+ eq(
+ sql`extract(month from granted)`,
+ sql`extract(month from current_date)`
+ )
+ )
+ )
+ )[0];
+
+ render(db, "dashboard", "dashboard", res, req, {
+ user,
+ moods,
+ moodsSorted,
+ moodHistory,
+ recentUpdates,
+ codes,
+ codesUsed,
+ feed: []
+ });
});
- });
- app.post("/update/mood", async (req, res) => {
- if (!req.session["loggedIn"]) {
- res.redirect("/login");
- return;
- }
- const moodIndex = moods.indexOf(req.body.mood.trim());
- if (moodIndex === -1) {
- req.flash(
- "error",
- "That mood doesn't exist in the database, WTF are you trying to do??"
- );
- res.redirect("/dashboard");
- return;
- }
- if (req.body.desc.length > 512) {
- req.flash(
- "error",
- "Mood description can't be longer than 512 characters"
- );
- res.redirect("/dashboard");
- }
+ app.post("/update/mood", async (req, res) => {
+ if (!req.session["loggedIn"]) {
+ res.redirect("/login");
+ return;
+ }
+ const moodIndex = moods.indexOf(req.body.mood.trim());
+ if (moodIndex === -1) {
+ req.flash(
+ "error",
+ "That mood doesn't exist in the database, WTF are you trying to do??"
+ );
+ res.redirect("/dashboard");
+ return;
+ }
+ if (req.body.desc.length > 512) {
+ req.flash(
+ "error",
+ "Mood description can't be longer than 512 characters"
+ );
+ res.redirect("/dashboard");
+ }
- await db
- .insert(updates)
- // @ts-expect-error
- .values({
- user: req.session["uid"],
- mood: moodIndex,
- description: req.body.desc,
- date: new Date(Date.now())
- });
- req.flash("success", "Mood updated!");
- res.redirect("/dashboard");
- });
+ await db
+ .insert(updates)
+ // @ts-expect-error
+ .values({
+ user: req.session["uid"],
+ mood: moodIndex,
+ description: req.body.desc,
+ date: new Date(Date.now())
+ });
+ req.flash("success", "Mood updated!");
+ res.redirect("/dashboard");
+ });
- // JOURNAL
- app.get("/journal", async (req, res) => {
- render(db, "journal", "Journal", res, req);
- });
- app.get("/journal/:id", async (req, res) => {
- const entry = (
- await db
- .select({
- uname: users.name,
- content: journalEntries.entry,
- date: journalEntries.date
- })
- .from(journalEntries)
- .where(eq(journalEntries.id, parseInt(req.params.id)))
- .leftJoin(users, eq(journalEntries.user, users.id))
- )[0];
- if (!entry) {
- //! TODO write a 404 page
- res.statusCode = 404;
- res.write("404 not found?? :(");
- return;
- }
- render(db, "journal_view", "Journal Entry", res, req, { entry });
- });
- app.post("/update/journal", async (req, res) => {
- if (!req.session["loggedIn"]) {
- res.redirect("/login");
- return;
- }
- if (req.body.description.length > 4096) {
- req.flash("error", "Entry too long!");
- res.redirect("/journal");
- return;
- }
+ // JOURNAL
+ app.get("/journal", async (req, res) => {
+ render(db, "journal", "your journal", res, req);
+ });
+ app.get("/journal/:id", async (req, res) => {
+ const entry = (
+ await db
+ .select({
+ uname: users.name,
+ title: journalEntries.title,
+ content: journalEntries.entry,
+ date: journalEntries.date
+ })
+ .from(journalEntries)
+ .where(eq(journalEntries.id, parseInt(req.params.id)))
+ .leftJoin(users, eq(journalEntries.user, users.id))
+ )[0];
+ if (!entry) {
+ render404(db, res, req);
+ return;
+ }
- const moodChange = parseInt(req.body.moodDelta);
- const visibility = parseInt(req.body.visibility);
- if (isNaN(moodChange) || isNaN(visibility)) {
- req.flash("error", "One of the values was improperly specified.");
- res.redirect("/journal");
- return;
- }
+ const entryTimestamp = dayjs(entry.date).fromNow();
+ render(db, "journal_view", entry.title, res, req, {
+ entry,
+ entryTimestamp
+ });
+ });
+ app.post("/update/journal", async (req, res) => {
+ if (!req.session["loggedIn"]) {
+ res.redirect("/login");
+ return;
+ }
+ if (req.body.title.length > 64) {
+ req.flash("error", "Title too long!");
+ res.redirect("/journal");
+ return;
+ }
+ if (req.body.description.length > 4096) {
+ req.flash("error", "Entry too long!");
+ res.redirect("/journal");
+ return;
+ }
- let id: number;
- try {
- const entry = await db
- .insert(journalEntries)
- // @ts-expect-error
- .values({
- user: req.session["uid"],
- moodChange,
- visibility,
- entry: req.body.description,
- date: new Date(Date.now())
- })
- .returning({ id: journalEntries.id });
- id = entry[0].id;
- } catch (err) {
- req.flash(
- "error",
- "Failed to create your entry. Try again later or send these logs to roxwize so she can know what's up:
" +
- err
- );
- res.redirect("/journal");
- return;
- }
- req.flash(
- "success",
- `Your journal entry is now available as #${id}!`
- );
- res.redirect("/journal");
- });
+ const moodChange = parseInt(req.body.moodDelta);
+ const visibility = parseInt(req.body.visibility);
+ if (isNaN(moodChange) || isNaN(visibility)) {
+ req.flash("error", "One of the values was improperly specified.");
+ res.redirect("/journal");
+ return;
+ }
+
+ let id: number;
+ try {
+ const entry = await db
+ .insert(journalEntries)
+ // @ts-expect-error
+ .values({
+ user: req.session["uid"],
+ moodChange,
+ visibility,
+ title: req.body.title,
+ entry: req.body.description
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll("\n", "
"),
+ date: new Date(Date.now())
+ })
+ .returning({ id: journalEntries.id });
+ id = entry[0].id;
+ } catch (err) {
+ req.flash(
+ "error",
+ "Failed to create your entry. Try again later or send these logs to roxwize so she can know what's up:
" +
+ err
+ );
+ res.redirect("/journal");
+ return;
+ }
+ req.flash(
+ "success",
+ `Your journal entry is now available as #${id}!`
+ );
+ res.redirect("/journal");
+ });
}
diff --git a/routes/users.ts b/routes/users.ts
index b4c6125..e7a9413 100644
--- a/routes/users.ts
+++ b/routes/users.ts
@@ -1,168 +1,216 @@
import { NodePgDatabase } from "drizzle-orm/node-postgres";
import { Express } from "express";
-import { follows, profiles, updates, users } from "../db/schema.js";
+import {
+ follows,
+ journalEntries,
+ profiles,
+ updates,
+ users
+} from "../db/schema.js";
import { and, desc, eq } from "drizzle-orm";
-import { getMoods, render, UserStatus } from "./util.js";
+import { getMoods, render, render404, UserStatus } from "./util.js";
import { PgColumn } from "drizzle-orm/pg-core";
import dayjs from "dayjs";
export default async function (app: Express, db: NodePgDatabase) {
- const { moods } = await getMoods();
- app.get("/users/:user", async (req, res) => {
- const isSelf = req.params.user === req.session["user"];
+ const { moods } = await getMoods();
+ app.get("/users/:user", async (req, res) => {
+ const isSelf = req.params.user === req.session["user"];
- const user = (
- await db
- .select({
- id: users.id,
- name: users.name,
- bio: profiles.bio,
- website: profiles.website
- })
- .from(users)
- .where(eq(users.name, req.params.user))
- .leftJoin(profiles, eq(profiles.user, users.id))
- )[0];
- if (!user) {
- req.flash("error", `User ${req.params.user} does not exist`);
- res.redirect("/");
- return;
- }
+ const user: {
+ id: number;
+ name: string;
+ registered: Date;
+ relativeRegistered?: string;
+ bio: string;
+ website: string;
+ } = (
+ await db
+ .select({
+ id: users.id,
+ name: users.name,
+ registered: users.registered,
+ bio: profiles.bio,
+ website: profiles.website
+ })
+ .from(users)
+ .where(eq(users.name, req.params.user))
+ .leftJoin(profiles, eq(profiles.user, users.id))
+ )[0];
+ if (!user) {
+ render404(db, res, req);
+ return;
+ }
+ user.relativeRegistered = dayjs(user.registered).fromNow();
- // follows
- const isFollowing = !!(
- await db
- .select()
- .from(follows)
- .where(
- and(
- eq(follows.followerId, req.session["uid"]),
- eq(follows.userId, user.id)
- )
- )
- .limit(1)
- )[0];
+ // follows
+ const isFollowing = !!(
+ await db
+ .select()
+ .from(follows)
+ .where(
+ and(
+ eq(follows.followerId, req.session["uid"]),
+ eq(follows.userId, user.id)
+ )
+ )
+ .limit(1)
+ )[0];
- // mood
- let moodSelection: { [k: string]: PgColumn } = {
- desc: updates.description,
- date: updates.date
- };
- if (!isSelf) moodSelection.mood = updates.mood;
- const userMood: { [k: string]: string | number | Date } = (
- await db
- .select(moodSelection)
- .from(updates)
- .where(eq(updates.user, user.id))
- .orderBy(desc(updates.date))
- .limit(1)
- )[0];
+ // mood
+ let moodSelection: { [k: string]: PgColumn } = {
+ desc: updates.description,
+ date: updates.date
+ };
+ if (!isSelf) moodSelection.mood = updates.mood;
+ const userMood: { [k: string]: string | number | Date } = (
+ await db
+ .select(moodSelection)
+ .from(updates)
+ .where(eq(updates.user, user.id))
+ .orderBy(desc(updates.date))
+ .limit(1)
+ )[0];
- // feed
- const now = dayjs();
- const userMoodFeed = (await db
- .select({
- mood: updates.mood,
- date: updates.date,
- desc: updates.description
- })
- .from(updates)
- .where(eq(updates.user, user.id))).map((e) => {
- return { user: user.name, mood: moods[e.mood], date: now.to(dayjs(e.date)), desc: e.desc }
- });
+ // journal entries
+ const now = dayjs();
+ const userJournalEntries = (
+ await db
+ .select({
+ id: journalEntries.id,
+ title: journalEntries.title,
+ date: journalEntries.date
+ })
+ .from(journalEntries)
+ .where(eq(journalEntries.user, user.id))
+ .orderBy(desc(journalEntries.date))
+ .limit(5)
+ ).map((e) => {
+ return {
+ id: e.id,
+ title: e.title,
+ date: e.date,
+ relativeDate: now.to(e.date)
+ };
+ });
- if (!isSelf) {
- userMood.mood = moods[userMood.mood as number];
- }
+ // feed
+ const userMoodFeed = (
+ await db
+ .select({
+ mood: updates.mood,
+ date: updates.date,
+ desc: updates.description
+ })
+ .from(updates)
+ .where(eq(updates.user, user.id))
+ .orderBy(desc(updates.date))
+ ).map((e) => {
+ return {
+ user: user.name,
+ mood: moods[e.mood],
+ date: e.date,
+ relativeDate: now.to(dayjs(e.date)),
+ desc: e.desc
+ };
+ });
- render(db, "user", `${req.params.user}'s Profile`, res, req, {
- user,
- isSelf,
- userMood,
- userMoodFeed,
- isFollowing
+ if (!isSelf) {
+ userMood.mood = moods[userMood.mood as number];
+ }
+
+ render(db, "user", `${req.params.user}'s profile`, res, req, {
+ user,
+ isSelf,
+ userMood,
+ userMoodFeed,
+ userJournalEntries,
+ isFollowing
+ });
});
- });
- app.post("/users/:user/edit", async (req, res) => {
- if (!req.session["loggedIn"]) {
- res.redirect("/login");
- return;
- }
- const { uname } = (
- await db
- .select({ uname: users.name })
- .from(users)
- .where(eq(users.name, req.params.user))
- )[0];
- if ((uname || "") !== req.session["user"] && !(req.session["status"] & UserStatus.MODERATOR)) {
- res.redirect("back");
- return;
- }
+ app.post("/users/:user/edit", async (req, res) => {
+ if (!req.session["loggedIn"]) {
+ res.redirect("/login");
+ return;
+ }
+ const { uname } = (
+ await db
+ .select({ uname: users.name })
+ .from(users)
+ .where(eq(users.name, req.params.user))
+ )[0];
+ if (
+ (uname || "") !== req.session["user"] &&
+ !(req.session["status"] & UserStatus.MODERATOR)
+ ) {
+ res.redirect("back");
+ return;
+ }
- await db //! no sanitization here either BROOOOOOO
- .update(profiles)
- .set({
- // @ts-expect-error
- bio: req.body.bio,
- website: req.body.website
- })
- .where(eq(profiles.user, req.session["uid"]));
- req.flash("success", "Profile updated!");
- res.redirect("/dashboard");
- });
- app.post("/users/:user/follow", async (req, res) => {
- if (!req.session["loggedIn"]) {
- res.redirect("/login");
- return;
- }
- if (req.session["user"] === req.params.user) {
- req.flash("error", "Can't Follow Yourself Dummy");
- res.redirect(`/users/${req.params.user}`);
- return;
- }
+ await db //! no sanitization here either BROOOOOOO
+ .update(profiles)
+ .set({
+ // @ts-expect-error
+ bio: req.body.bio,
+ website: req.body.website
+ })
+ .where(eq(profiles.user, req.session["uid"]));
+ req.flash("success", "Profile updated!");
+ res.redirect("/dashboard");
+ });
+ app.post("/users/:user/follow", async (req, res) => {
+ if (!req.session["loggedIn"]) {
+ res.redirect("/login");
+ return;
+ }
+ if (req.session["user"] === req.params.user) {
+ req.flash("error", "Can't Follow Yourself Dummy");
+ res.redirect(`/users/${req.params.user}`);
+ return;
+ }
- const { uid } = (
- await db
- .select({ uid: users.id })
- .from(users)
- .where(eq(users.name, req.params.user))
- )[0];
- if (!uid) {
- req.flash(
- "error",
- "It looks like you're trying to follow a user that doesn't exist anymore."
- );
- res.redirect("/");
- return;
- }
- const isFollowing = !!(
- await db
- .select()
- .from(follows)
- .where(
- and(
- eq(follows.followerId, req.session["uid"]),
- eq(follows.userId, uid)
- )
- )
- .limit(1)
- )[0];
- if (isFollowing) {
- // unfollow
- await db
- .delete(follows)
- .where(
- and(
- eq(follows.followerId, req.session["uid"]),
- eq(follows.userId, uid)
- )
- );
- } else {
- await db.insert(follows).values({
- userId: uid,
- followerId: req.session["uid"]
- });
- }
- res.redirect(`/users/${req.params.user}`);
- });
+ const { uid } = (
+ await db
+ .select({ uid: users.id })
+ .from(users)
+ .where(eq(users.name, req.params.user))
+ )[0];
+ if (!uid) {
+ req.flash(
+ "error",
+ "It looks like you're trying to follow a user that doesn't exist anymore."
+ );
+ res.redirect("/");
+ return;
+ }
+ const isFollowing = !!(
+ await db
+ .select()
+ .from(follows)
+ .where(
+ and(
+ eq(follows.followerId, req.session["uid"]),
+ eq(follows.userId, uid)
+ )
+ )
+ .limit(1)
+ )[0];
+ if (isFollowing) {
+ // unfollow
+ await db
+ .delete(follows)
+ .where(
+ and(
+ eq(follows.followerId, req.session["uid"]),
+ eq(follows.userId, uid)
+ )
+ );
+ } else {
+ await db.insert(follows).values({
+ userId: uid,
+ followerId: req.session["uid"]
+ });
+ }
+ res.redirect(`/users/${req.params.user}`);
+ });
}
diff --git a/routes/util.ts b/routes/util.ts
index 910d062..16c2432 100644
--- a/routes/util.ts
+++ b/routes/util.ts
@@ -5,92 +5,113 @@ import { count, desc, eq } from "drizzle-orm";
import fs from "node:fs/promises";
export enum UserStatus {
- MODERATOR = 0b001,
- BANNED = 0b010,
- TRUSTED = 0b100
-};
+ MODERATOR = 0b001,
+ BANNED = 0b010,
+ TRUSTED = 0b100
+}
const nonceChars =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_";
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_";
let nonce: string;
export function setNonce() {
- nonce = "";
- for (let i = 0; i < 32; i++)
- nonce += nonceChars[Math.floor(Math.random() * nonceChars.length)];
- return nonce;
+ nonce = "";
+ for (let i = 0; i < 32; i++)
+ nonce += nonceChars[Math.floor(Math.random() * nonceChars.length)];
+ return nonce;
}
export function getNonce() {
- if (!nonce)
- throw new Error("Nonce doesn't exist");
- return nonce;
+ if (!nonce) throw new Error("Nonce doesn't exist");
+ return nonce;
}
let moods: string[], moodsSorted: string[];
export async function getMoods() {
- if (!moods)
- moods = (await fs.readFile("./static/moods.txt"))
- .toString("utf-8")
- .split(";");
- if (!moodsSorted) moodsSorted = Array.from(moods).sort();
- return { moods, moodsSorted };
+ if (!moods)
+ moods = (await fs.readFile("./static/moods.txt"))
+ .toString("utf-8")
+ .split(";");
+ if (!moodsSorted) moodsSorted = Array.from(moods).sort();
+ return { moods, moodsSorted };
}
export async function render(
- db: NodePgDatabase,
- page: string,
- title: string,
- res: Response,
- req: Request,
- stuff?: Object
+ db: NodePgDatabase,
+ page: string,
+ title: string,
+ res: Response,
+ req: Request,
+ stuff?: Object
) {
- //? maybe you should cache this and save the current mood to the session until it's changed
- const { moods } = await getMoods();
- let currentMood: string;
- if (req.session["loggedIn"]) {
- const update = (
- await db
- .select({ mood: updates.mood })
- .from(updates)
- .where(eq(updates.user, req.session["uid"]))
- .orderBy(desc(updates.date))
- .limit(1)
- )[0];
- currentMood = moods[update?.mood];
- }
-
- const o = {
- title,
- session: req.session,
- flashes: req.flash(),
- moods,
- currentMood,
- nonce
- };
- res.render(page, { ...o, ...stuff });
-}
-
-const inviteCodeChars = "abcdefghijklmnopqrstuvwxyz0123456789"
-export async function createInviteCode(db: NodePgDatabase, user: number, expires: Date, confers = 0) {
- let existingToken = 1, token: string;
- while (existingToken) {
- token = user.toString().padStart(4, "0") + "-"
- for (let i = 0; i < 17; i++) {
- if ((i + 1) % 6 === 0) {
- token += "-";
- continue;
- }
- token += inviteCodeChars[Math.floor(Math.random() * inviteCodeChars.length)];
+ //? maybe you should cache this and save the current mood to the session until it's changed
+ const { moods } = await getMoods();
+ let currentMood: string;
+ if (req.session["loggedIn"]) {
+ const update = (
+ await db
+ .select({ mood: updates.mood })
+ .from(updates)
+ .where(eq(updates.user, req.session["uid"]))
+ .orderBy(desc(updates.date))
+ .limit(1)
+ )[0];
+ currentMood = moods[update?.mood];
}
- existingToken = (await db.select({ value: count() }).from(inviteCodes).where(eq(inviteCodes.token, token)))[0].value;
- }
- //@ts-expect-error
- await db.insert(inviteCodes).values({
- token,
- user: user || undefined,
- granted: new Date(Date.now()),
- expires,
- confers
- });
- return token;
+ const o = {
+ title,
+ session: req.session,
+ flashes: req.flash(),
+ moods,
+ currentMood,
+ nonce
+ };
+ res.render(page, { ...o, ...stuff });
+}
+export async function render404(
+ db: NodePgDatabase,
+ res: Response,
+ req: Request
+) {
+ res.statusCode = 404;
+ render(db, "404", "not found", res, req);
+}
+
+const inviteCodeChars = "abcdefghijklmnopqrstuvwxyz0123456789";
+export async function createInviteCode(
+ db: NodePgDatabase,
+ user: number,
+ expires: Date,
+ confers = 0
+) {
+ let existingToken = 1,
+ token: string;
+ while (existingToken) {
+ token = user.toString().padStart(4, "0") + "-";
+ for (let i = 0; i < 17; i++) {
+ if ((i + 1) % 6 === 0) {
+ token += "-";
+ continue;
+ }
+ token +=
+ inviteCodeChars[
+ Math.floor(Math.random() * inviteCodeChars.length)
+ ];
+ }
+ existingToken = (
+ await db
+ .select({ value: count() })
+ .from(inviteCodes)
+ .where(eq(inviteCodes.token, token))
+ )[0].value;
+ }
+
+ //@ts-expect-error
+ await db.insert(inviteCodes).values({
+ token,
+ user: user || undefined,
+ granted: new Date(Date.now()),
+ expires,
+ confers
+ });
+ return token;
}
diff --git a/static/css/main.css b/static/css/main.css
index 781a589..5f1a3c1 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -8,6 +8,8 @@ body {
justify-content: center;
margin: 0;
background-color: #21211d;
+ background-image: url("/img/bg.png");
+ background-size: cover;
width: 100vw;
height: 100vh;
font-family: "Gohufont 14", sans-serif;
@@ -17,6 +19,7 @@ body {
main {
background-color: #faf8da;
width: 95%;
+ max-width: 953px;
height: 95%;
}
diff --git a/static/img/bg.png b/static/img/bg.png
new file mode 100644
index 0000000..4cf4230
Binary files /dev/null and b/static/img/bg.png differ
diff --git a/views/404.pug b/views/404.pug
new file mode 100644
index 0000000..9f92e7f
--- /dev/null
+++ b/views/404.pug
@@ -0,0 +1,5 @@
+extends site.pug
+
+block content
+ h1 Not Found
+ p No such thing is to be found here!! Perhaps, you would be better off, locating something that exists.
diff --git a/views/_feed.pug b/views/_feed.pug
index 6ee981e..46c0b63 100644
--- a/views/_feed.pug
+++ b/views/_feed.pug
@@ -9,6 +9,6 @@ mixin feed(feed, hideUser)
| #{update.user}
strong= update.mood
div= update.desc || "[no mood description provided]"
- div= update.date
+ div(title=update.date.toLocaleString())= update.relativeDate
else
span [no updates]
diff --git a/views/journal.pug b/views/journal.pug
index ab29489..62d2f5c 100644
--- a/views/journal.pug
+++ b/views/journal.pug
@@ -30,6 +30,9 @@ block content
input.ovm-input(type="radio", name="moodDelta", id="moodDelta-mw", value="-2", required)
label.ovm-input(for="moodDelta-mw", title="Much worse")
img(src="/img/downdown.svg", alt="Much worse")
+ .input
+ label(for="title") Title
+ input(type="text", name="title", id="title", placeholder="max 64 chars", maxlength="64", style="width:66ch;")
.input
label(for="description") Journal entry for today
textarea(name="description", id="description", placeholder="max 4096 chars", maxlength="4096", cols="60", rows="12")
diff --git a/views/journal_view.pug b/views/journal_view.pug
index e759ed9..2cfecad 100644
--- a/views/journal_view.pug
+++ b/views/journal_view.pug
@@ -1,13 +1,12 @@
extends site.pug
block content
- h1= entry.date.toLocaleDateString()
+ h1= entry.title
span
- | by
- |
+ | by
a(href=`/users/${entry.uname}`)= entry.uname
- |
- | at #{entry.date.toLocaleTimeString()}
+ |
+ span(title=entry.date.toLocaleString()) #{entryTimestamp}
br
br
- div= entry.content
+ div!= entry.content
diff --git a/views/site.pug b/views/site.pug
index 46a2bf2..3a58ab7 100644
--- a/views/site.pug
+++ b/views/site.pug
@@ -11,7 +11,7 @@ html(lang="en")
main
header
a(href="/")
- img#header-logo(src="/img/logo.svg", alt="logo")
+ img#header-logo(src="/img/logo.svg", alt="logo", title="meow")
nav
if session.loggedIn
if session.status & 0b001
diff --git a/views/user.pug b/views/user.pug
index e515e48..4b2d390 100644
--- a/views/user.pug
+++ b/views/user.pug
@@ -2,20 +2,21 @@ extends site.pug
//- Display mood here but keep yourself dry (i.e. dont request user mood if the profile being viewed is the profile of the currently logged-in user)
block content
- h1= user.name
- if !isSelf
- form(action=`/users/${user.name}/follow`, method="post")
- button(type="submit")= isFollowing ? "unfollow" : "follow"
- br
- span= user.bio || "[no bio]"
+ div(style="display:flex;align-items:center;gap:8px;")
+ h1= user.name
+ if !isSelf
+ form(action=`/users/${user.name}/follow`, method="post")
+ button(type="submit")= isFollowing ? "unfollow" : "follow"
+ br
+ div(style="margin-bottom:1em;")
+ | Joined
+ span(title=user.registered.toLocaleString())= user.relativeRegistered
+ div= user.bio || "[no bio]"
if user.website
- br
a(href=user.website)= user.website
- br
- br
- h2 Current mood
+ h2(style="margin-top:1em;") Current mood
if userMood
- span
+ div
| Feeling
|
strong
@@ -23,11 +24,20 @@ block content
| #{currentMood}
else
| #{userMood.mood}
- br
div(style="margin-left:2ch;word-wrap:break-word;")= userMood.desc || "[no mood description]"
else
- span User has not yet set a mood!
+ div User has not yet set a mood!
br
- h2 Recent updates
+ h2 Recent journal entries
+ if userJournalEntries.length > 0
+ ul
+ for entry of userJournalEntries
+ li
+ a(href=`/journal/${entry.id}`)= entry.title
+ span.subtle(title=entry.date.toLocaleString()) (#{entry.relativeDate})
+ else
+ div [no entries]
+ br
+ h2 Recent mood updates
include _feed.pug
+feed(userMoodFeed, true)