1
0
Fork 0
mirror of https://git.sr.ht/~roxwize/mipilin synced 2025-01-31 02:53:36 +00:00

invite cooooddeesss

Signed-off-by: roxwize <rae@roxwize.xyz>
This commit is contained in:
Rae 5e 2024-12-10 22:27:43 -05:00
parent 7b563f5c31
commit 5abe0b5fad
Signed by: rae
GPG key ID: 5B1A0FAB9BAB81EE
22 changed files with 2326 additions and 16 deletions

3
TODO.md Normal file
View file

@ -0,0 +1,3 @@
- [ ] An audit log
- [ ] Actual invite code pruning
- [ ] Make the journal work (i dont really remember what was left out though)

View file

@ -29,7 +29,9 @@ export const updates = pgTable("updates", {
export const journalEntries = pgTable("journal_entries", { export const journalEntries = pgTable("journal_entries", {
id: integer().primaryKey().generatedAlwaysAsIdentity(), id: integer().primaryKey().generatedAlwaysAsIdentity(),
user: integer().references(() => users.id, { onDelete: "cascade" }).notNull(), user: integer()
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
moodChange: integer("mood-change").default(0).notNull(), moodChange: integer("mood-change").default(0).notNull(),
entry: varchar({ length: 4096 }).default("").notNull(), entry: varchar({ length: 4096 }).default("").notNull(),
visibility: integer().default(1).notNull(), visibility: integer().default(1).notNull(),
@ -61,3 +63,11 @@ export const follows = pgTable(
}; };
} }
); );
export const inviteCodes = pgTable("invite_codes", {
token: varchar({ length: 22 }).primaryKey(),
user: integer("user_id").references(() => users.id, { onDelete: "cascade" }),
granted: timestamp().notNull(),
expires: timestamp().default(new Date(0)),
confersModerator: boolean("confers_moderator").default(false)
});

View file

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS "invite_codes" (
"token" varchar(8) PRIMARY KEY NOT NULL,
"user_id" integer,
"granted" timestamp NOT NULL,
"expires" timestamp DEFAULT '1970-01-01 00:00:00.000'
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "invite_codes" ADD CONSTRAINT "invite_codes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -0,0 +1,2 @@
ALTER TABLE "invite_codes" ALTER COLUMN "token" SET DATA TYPE varchar(20);--> statement-breakpoint
ALTER TABLE "invite_codes" ADD COLUMN "confersAdmin" boolean DEFAULT false;

View file

@ -0,0 +1 @@
ALTER TABLE "invite_codes" RENAME COLUMN "confersAdmin" TO "confers_admin";

View file

@ -0,0 +1 @@
ALTER TABLE "invite_codes" RENAME COLUMN "confers_admin" TO "confers_moderator";

View file

@ -0,0 +1 @@
ALTER TABLE "invite_codes" ALTER COLUMN "token" SET DATA TYPE varchar(22);

View file

@ -0,0 +1,408 @@
{
"id": "f40891bb-8a67-4d29-8772-b05615e0aa9f",
"prevId": "73a239ee-a945-48e0-a021-d415815a830b",
"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(8)",
"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'"
}
},
"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
},
"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
},
"moderator": {
"name": "moderator",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"banned": {
"name": "banned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"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": {}
}
}

View file

@ -0,0 +1,415 @@
{
"id": "09a82ff0-6a18-4587-a33f-a55c2fef2ebf",
"prevId": "f40891bb-8a67-4d29-8772-b05615e0aa9f",
"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(20)",
"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'"
},
"confersAdmin": {
"name": "confersAdmin",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"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
},
"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
},
"moderator": {
"name": "moderator",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"banned": {
"name": "banned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"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": {}
}
}

View file

@ -0,0 +1,415 @@
{
"id": "49ea990a-0039-44d5-a35e-319db2299a46",
"prevId": "09a82ff0-6a18-4587-a33f-a55c2fef2ebf",
"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(20)",
"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_admin": {
"name": "confers_admin",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"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
},
"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
},
"moderator": {
"name": "moderator",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"banned": {
"name": "banned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"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": {}
}
}

View file

@ -0,0 +1,415 @@
{
"id": "61539639-4e1f-4bf2-854e-1657c5024c6c",
"prevId": "49ea990a-0039-44d5-a35e-319db2299a46",
"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(20)",
"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_moderator": {
"name": "confers_moderator",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"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
},
"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
},
"moderator": {
"name": "moderator",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"banned": {
"name": "banned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"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": {}
}
}

View file

@ -0,0 +1,415 @@
{
"id": "d928a4c4-ab43-4979-b6aa-3892f29701c8",
"prevId": "61539639-4e1f-4bf2-854e-1657c5024c6c",
"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_moderator": {
"name": "confers_moderator",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"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
},
"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
},
"moderator": {
"name": "moderator",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"banned": {
"name": "banned",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"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": {}
}
}

View file

@ -50,6 +50,41 @@
"when": 1731868543611, "when": 1731868543611,
"tag": "0006_lame_grey_gargoyle", "tag": "0006_lame_grey_gargoyle",
"breakpoints": true "breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1733871933739,
"tag": "0007_silly_freak",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1733873221416,
"tag": "0008_huge_pyro",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1733873440944,
"tag": "0009_unusual_ozymandias",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1733874003964,
"tag": "0010_exotic_ted_forrester",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1733874202840,
"tag": "0011_loose_dreadnoughts",
"breakpoints": true
} }
] ]
} }

21
main.ts
View file

@ -5,21 +5,22 @@ import "dotenv/config";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js"; import relativeTime from "dayjs/plugin/relativeTime.js";
import express from "express"; import express, { Express } from "express";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
import session from "express-session"; import session from "express-session";
import connectPgSimple from "connect-pg-simple"; import connectPgSimple from "connect-pg-simple";
import flash from "connect-flash"; import flash from "connect-flash";
import { drizzle } from "drizzle-orm/node-postgres"; import { drizzle, NodePgDatabase } from "drizzle-orm/node-postgres";
import { updates, users } from "./db/schema.js"; import { updates, users } from "./db/schema.js";
import { desc, eq } from "drizzle-orm"; import { count, desc, eq } from "drizzle-orm";
// routes // routes
import adminRoutes from "./routes/admin.js";
import loginRoutes from "./routes/login.js"; import loginRoutes from "./routes/login.js";
import userRoutes from "./routes/users.js"; import userRoutes from "./routes/users.js";
import updateRoutes from "./routes/updates.js"; import updateRoutes from "./routes/updates.js";
import { getMoods, render, setNonce } from "./routes/util.js"; import { createInviteCode, getMoods, render, setNonce } from "./routes/util.js";
const db = drizzle(process.env.DATABASE_URL!); const db = drizzle(process.env.DATABASE_URL!);
@ -58,6 +59,8 @@ object-src 'none'; base-uri 'none';"
return next(); return next();
}); });
await init(app, db);
app.get("/", async (req, res) => { app.get("/", async (req, res) => {
const upd = db const upd = db
.selectDistinctOn([updates.user], { .selectDistinctOn([updates.user], {
@ -100,6 +103,7 @@ object-src 'none'; base-uri 'none';"
}); });
}); });
adminRoutes(app, db);
await userRoutes(app, db); await userRoutes(app, db);
await updateRoutes(app, db); await updateRoutes(app, db);
loginRoutes(app, db); loginRoutes(app, db);
@ -108,3 +112,12 @@ object-src 'none'; base-uri 'none';"
console.log("Listening on http://127.0.0.1:1337/"); console.log("Listening on http://127.0.0.1:1337/");
}); });
})(); })();
async function init(app: Express, db: NodePgDatabase) {
const totalUsers = await db.select({ value: count() }).from(users);
if (totalUsers[0].value === 0) {
console.log("There are no users registered. Creating a temporary invite code right now.");
console.log(" " + await createInviteCode(db, 0, new Date(Date.now() + (1 * 1000 * 60)), true));
console.log("This code expires in 1 minute and will confer admin powers to whoever signs up with it.");
}
}

60
routes/admin.ts Normal file
View file

@ -0,0 +1,60 @@
import { NodePgDatabase } from "drizzle-orm/node-postgres";
import { Express } from "express";
import { createInviteCode, render } from "./util.js";
import { inviteCodes, users } from "../db/schema.js";
import { desc, eq } from "drizzle-orm";
import dayjs from "dayjs";
export default function (app: Express, db: NodePgDatabase) {
app.get("/mod", async (req, res) => {
if (!req.session["loggedIn"] || !req.session["moderator"]) {
res.redirect("/");
return;
}
const now = dayjs();
const codes = (
await db
.select({ expires: inviteCodes.expires, token: inviteCodes.token, uname: users.name })
.from(inviteCodes)
.leftJoin(users, eq(inviteCodes.user, users.id))
.orderBy(desc(inviteCodes.granted))
).map((e) => {
return {
expires: e.expires,
token: e.token,
uname: e.uname,
expiresString: now.to(dayjs(e.expires))
};
});
render(db, "admin", "Admin Panel", res, req, { codes });
});
app.post("/mod/codes/delete", async (req, res) => {
if (!req.session["loggedIn"] || !req.session["moderator"]) {
res.redirect("/");
return;
}
await db.delete(inviteCodes).where(eq(inviteCodes.token, req.body.token));
req.flash("success", "Deleted.");
res.redirect("/mod");
})
app.post("/mod/codes/create", async (req, res) => {
if (!req.session["loggedIn"] || !req.session["moderator"]) {
res.redirect("/");
return;
}
const expiration = new Date(req.body.expiration || 0);
if (req.body.expiration && expiration.getTime() <= Date.now()) {
req.flash("error", "Chosen expiration date is in the past.");
res.redirect("/mod");
return;
}
const code = await createInviteCode(db, req.session["uid"], expiration);
req.flash("success", `Your code has been created as <b>${code}</b>.`);
res.redirect("/mod");
});
}

View file

@ -2,7 +2,7 @@ import { Express } from "express";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { render } from "./util.js"; import { render } from "./util.js";
import { NodePgDatabase } from "drizzle-orm/node-postgres"; import { NodePgDatabase } from "drizzle-orm/node-postgres";
import { profiles, users } from "../db/schema.js"; import { inviteCodes, profiles, users } from "../db/schema.js";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
//! TEMP Also not sanitized like at all //! TEMP Also not sanitized like at all
@ -20,6 +20,12 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/"); res.redirect("/");
return; return;
} }
// validation
if (req.body.referral.length < 22) {
req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
res.redirect("/register");
return;
}
if (req.body.name.length < 3) { if (req.body.name.length < 3) {
req.flash("error", "Username can't be shorter than 3 characters"); req.flash("error", "Username can't be shorter than 3 characters");
res.redirect("/register"); res.redirect("/register");
@ -43,7 +49,24 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/register"); res.redirect("/register");
return; return;
} }
// invite code checking
const code = (await db.select({ expires: inviteCodes.expires, confersModerator: inviteCodes.confersModerator }).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 ( if (
(await db.select().from(users).where(eq(users.name, req.body.name))) (await db.select().from(users).where(eq(users.name, req.body.name)))
.length > 0 .length > 0
@ -66,10 +89,12 @@ export default function(app: Express, db: NodePgDatabase) {
const { uid } = ( const { uid } = (
await db await db
.insert(users) .insert(users)
//@ts-expect-error
.values({ .values({
name: req.body.name, name: req.body.name,
email: req.body.email, //! Not actually validating this like at all??? email: req.body.email, //! Not actually validating this like at all???
pass: hash, pass: hash,
moderator: code.confersModerator,
registered: new Date(Date.now()) registered: new Date(Date.now())
}) })
.returning({ uid: users.id }) .returning({ uid: users.id })
@ -77,6 +102,7 @@ export default function(app: Express, db: NodePgDatabase) {
await db.insert(profiles).values({ user: uid }); await db.insert(profiles).values({ user: uid });
req.session["loggedIn"] = true; req.session["loggedIn"] = true;
req.session["moderator"] = code.confersModerator;
req.session["user"] = req.body.name; req.session["user"] = req.body.name;
req.session["uid"] = uid; req.session["uid"] = uid;
req.flash( req.flash(
@ -106,16 +132,17 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/login"); res.redirect("/login");
return; return;
} }
if (await bcrypt.compare(req.body.pass, user.pass)) { if (!(await bcrypt.compare(req.body.pass, user.pass))) {
req.session["loggedIn"] = true;
req.session["user"] = user.name;
req.session["uid"] = user.id;
req.flash("success", "You're logged in! Welcome back!!");
res.redirect("/dashboard");
} else {
req.flash("error", "The username or password is invalid! I'm sorry! :("); req.flash("error", "The username or password is invalid! I'm sorry! :(");
res.redirect("/login"); res.redirect("/login");
return;
} }
req.session["loggedIn"] = true;
req.session["moderator"] = user.moderator;
req.session["user"] = user.name;
req.session["uid"] = user.id;
req.flash("success", "You're logged in! Welcome back!!");
res.redirect("/dashboard");
}); });
app.get("/logout", (req, res) => { app.get("/logout", (req, res) => {
req.session["loggedIn"] = false; req.session["loggedIn"] = false;

View file

@ -1,7 +1,7 @@
import { NodePgDatabase } from "drizzle-orm/node-postgres"; import { NodePgDatabase } from "drizzle-orm/node-postgres";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { updates } from "../db/schema.js"; import { inviteCodes, updates } from "../db/schema.js";
import { desc, eq } from "drizzle-orm"; import { count, desc, eq } from "drizzle-orm";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
const nonceChars = const nonceChars =
@ -62,3 +62,29 @@ export async function render(
}; };
res.render(page, { ...o, ...stuff }); res.render(page, { ...o, ...stuff });
} }
const inviteCodeChars = "abcdefghijklmnopqrstuvwxyz0123456789"
export async function createInviteCode(db: NodePgDatabase, user: number, expires: Date, confersModerator = false) {
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,
confersModerator
});
return token;
}

View file

@ -148,6 +148,13 @@ summary {
border-color: #3da758; border-color: #3da758;
} }
.error {
color: #c51e48;
}
.subtle {
opacity: 0.5;
}
button, button,
textarea, textarea,
select, select,
@ -169,6 +176,14 @@ select {
cursor: pointer; cursor: pointer;
} }
table button {
padding: 0 4px;
}
th, td {
text-align: left;
padding-right: 1em;
}
.feed-update { .feed-update {
background-color: #d5d4bb; background-color: #d5d4bb;
height: fit-content; height: fit-content;

32
views/admin.pug Normal file
View file

@ -0,0 +1,32 @@
extends site.pug
block content
h1 Admin Panel
h2 Invite codes
form(action="/mod/codes/delete", method="post")
table
tbody
tr
th Token
th Expires
th Creator
for code of codes
- const timestamp = code.expires.getTime()
tr
td= code.token
if timestamp === 0
td.subtle never
else if Date.now() >= timestamp
td.error EXPIRED
else
td= code.expiresString
td
a(href=`/users/${code.uname}`)= code.uname
td
button(type="submit", name="token", value=code.token) Delete
form(action="/mod/codes/prune", method="post")
button(type="submit") Prune
br
form(action="/mod/codes/create", method="post")
input(type="datetime-local", name="expiration")
button(type="submit") Create

View file

@ -36,7 +36,7 @@ block content
h1(style="margin-top:0.5em;") Feed h1(style="margin-top:0.5em;") Feed
include _feed.pug include _feed.pug
+feed(recentUpdates) +feed(recentUpdates)
script. script(nonce=nonce).
function disable(form) { function disable(form) {
const btn = form.querySelector("button"); const btn = form.querySelector("button");
btn.setAttribute("disabled", true); btn.setAttribute("disabled", true);

View file

@ -9,4 +9,6 @@ block content
br br
input(type="password", name="pass", placeholder="Password", required) input(type="password", name="pass", placeholder="Password", required)
br br
input(type="text", name="referral", placeholder="Invite Code", maxlength="22", required)
br
button(type="submit") Sign Up button(type="submit") Sign Up

View file

@ -14,6 +14,8 @@ html(lang="en")
img#header-logo(src="/img/logo.svg", alt="logo") img#header-logo(src="/img/logo.svg", alt="logo")
nav nav
if session.loggedIn if session.loggedIn
if session.moderator
a(href="/mod/") Admin
a(href="/dashboard/") Dashboard a(href="/dashboard/") Dashboard
a(href="/journal/") Journal a(href="/journal/") Journal
a(href="/logout/") Log out a(href="/logout/") Log out