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

Some iprovements idfk

Signed-off-by: roxwize <rae@roxwize.xyz>
This commit is contained in:
Rae 5e 2025-01-27 23:34:46 -05:00
parent a3e6df6ce8
commit 961d963677
Signed by: rae
GPG key ID: 5B1A0FAB9BAB81EE
13 changed files with 160 additions and 102 deletions

View file

@ -1,13 +1,14 @@
- [ ] An audit log - [ ] An audit log
- [ ] Invite code pruning - [ ] Invite code pruning
- [ ] Make the journal work (i dont really remember what was left out though) - [x] Make the journal work (i dont really remember what was left out though)
- [x] Write a 404 page - [x] Write a 404 page
- [ ] Make better login pages - [ ] Make better login pages
- [ ] You do realize using toLocaleString on the server only makes it use your locale right - [ ] You do realize using toLocaleString on the server only makes it use your locale right
- [ ] Make recent updates also account for new journal entries - [ ] Make recent updates also account for new journal entries
- [ ] View all previous moods and journal entries - [ ] View all previous moods and journal entries
- [ ] Visibility indicator on journal entries - [x] Visibility indicator on journal entries
- [ ] Hide journal entries from feed that are hidden unless current user is a moderator - [x] Hide journal entries from feed that are hidden unless current user is a moderator
- [ ] Edit/delete journal entries? - [ ] Edit/delete journal entries?
- [ ] Journal entry comments - [ ] Journal entry comments
- [ ] A Forum - [ ] A Forum
- [ ] Make it easier to view journal entries

View file

@ -126,7 +126,7 @@ async function init(app: Express, db: NodePgDatabase) {
const totalUsers = await db.select({ value: count() }).from(users); const totalUsers = await db.select({ value: count() }).from(users);
if (totalUsers[0].value === 0) { if (totalUsers[0].value === 0) {
console.log("There are no users registered. Creating a temporary invite code right now."); 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)), UserStatus.MODERATOR)); console.log(" " + await createInviteCode(db, 0, new Date(Date.now() + (10 * 1000 * 60)), UserStatus.MODERATOR));
console.log("This code expires in 1 minute and will confer admin powers to whoever signs up with it."); console.log("This code expires in 10 minutes and will confer admin powers to whoever signs up with it.");
} }
} }

View file

@ -5,96 +5,114 @@ import { inviteCodes, users } from "../db/schema.js";
import { and, count, desc, eq, sql } from "drizzle-orm"; import { and, count, desc, eq, sql } from "drizzle-orm";
import dayjs from "dayjs"; import dayjs from "dayjs";
const USER_REFERRAL_EXPIRATION = 7 * 24 * 60 * 60 * 1000 const USER_REFERRAL_EXPIRATION = 7 * 24 * 60 * 60 * 1000;
export default function (app: Express, db: NodePgDatabase) { export default function (app: Express, db: NodePgDatabase) {
app.get("/mod", async (req, res) => { app.get("/mod", async (req, res) => {
if ( if (
!req.session["loggedIn"] || !req.session["loggedIn"] ||
!(req.session["status"] & UserStatus.MODERATOR) !(req.session["status"] & UserStatus.MODERATOR)
) { ) {
render404(db, res, req); render404(db, res, req);
return; return;
} }
const now = dayjs(); const now = dayjs();
const codes = ( const codes = (
await db await db
.select({ .select({
expires: inviteCodes.expires, expires: inviteCodes.expires,
token: inviteCodes.token, token: inviteCodes.token,
uname: users.name uname: users.name
}) })
.from(inviteCodes) .from(inviteCodes)
.leftJoin(users, eq(inviteCodes.user, users.id)) .leftJoin(users, eq(inviteCodes.user, users.id))
.orderBy(desc(inviteCodes.granted)) .orderBy(desc(inviteCodes.granted))
).map((e) => { ).map((e) => {
return { return {
expires: e.expires, expires: e.expires,
token: e.token, token: e.token,
uname: e.uname, uname: e.uname,
expiresString: now.to(dayjs(e.expires)) expiresString: now.to(dayjs(e.expires || 0))
}; };
});
// TODO: also add a last login field to the user schema (and paginate this area)
const userTable = await db
.select({ uname: users.name, id: users.id, status: users.status })
.from(users)
.orderBy(desc(users.registered))
.limit(10);
render(db, "admin", "admin panel", res, req, { codes, userTable });
}); });
render(db, "admin", "admin panel", res, req, { codes });
});
app.post("/codes/delete", async (req, res) => { app.post("/codes/delete", async (req, res) => {
if ( if (
!req.session["loggedIn"] || !req.session["loggedIn"] ||
!(req.session["status"] & UserStatus.MODERATOR) !(req.session["status"] & UserStatus.MODERATOR)
) { ) {
res.redirect("/"); res.redirect("/");
return; return;
} }
await db.delete(inviteCodes).where(eq(inviteCodes.token, req.body.token));
req.flash("success", "Deleted.");
res.redirect("/mod");
});
app.post("/codes/create", async (req, res) => {
if (
!req.session["loggedIn"]
) {
res.redirect("/login");
return;
}
if (!(req.session["status"] & UserStatus.MODERATOR)) {
const { codesUsed } = (
await db await db
.select({ codesUsed: count() }) .delete(inviteCodes)
.from(inviteCodes) .where(eq(inviteCodes.token, req.body.token));
.where( req.flash("success", "Deleted.");
and( res.redirect("/mod");
eq(inviteCodes.user, req.session["uid"]), });
eq( app.post("/codes/create", async (req, res) => {
sql`extract(month from granted)`, if (!req.session["loggedIn"]) {
sql`extract(month from current_date)` res.redirect("/login");
) return;
) }
) if (!(req.session["status"] & UserStatus.MODERATOR)) {
)[0]; const { codesUsed } = (
if (codesUsed >= 5) { await db
req.flash("error", "You've generated the maximum of five codes this week. Your counter will reset next month."); .select({ codesUsed: count() })
res.redirect("/dashboard"); .from(inviteCodes)
return; .where(
} and(
eq(inviteCodes.user, req.session["uid"]),
eq(
sql`extract(month from granted)`,
sql`extract(month from current_date)`
)
)
)
)[0];
if (codesUsed >= 5) {
req.flash(
"error",
"You've generated the maximum of five codes this week. Your counter will reset next month."
);
res.redirect("/dashboard");
return;
}
const code = await createInviteCode(db, req.session["uid"], new Date(Date.now() + USER_REFERRAL_EXPIRATION)); const code = await createInviteCode(
req.flash("success", `Your code has been created as <b>${code}</b>. It expires in a week so use it ASAP!!!`); db,
res.redirect("/dashboard"); req.session["uid"],
return; new Date(Date.now() + USER_REFERRAL_EXPIRATION)
} );
req.flash(
"success",
`Your code has been created as <b>${code}</b>. It expires in a week so use it ASAP!!!`
);
res.redirect("/dashboard");
return;
}
const expiration = new Date(req.body.expiration || 0); const expiration = new Date(req.body.expiration || 0);
if (req.body.expiration && expiration.getTime() <= Date.now()) { if (req.body.expiration && expiration.getTime() <= Date.now()) {
req.flash("error", "Chosen expiration date is in the past."); req.flash("error", "Chosen expiration date is in the past.");
res.redirect("/mod"); res.redirect("/mod");
return; return;
} }
const code = await createInviteCode(db, req.session["uid"], expiration); const code = await createInviteCode(db, req.session["uid"], expiration);
req.flash("success", `Your code has been created as <b>${code}</b>.`); req.flash("success", `Your code has been created as <b>${code}</b>.`);
res.redirect("/mod"); res.redirect("/mod");
}); });
} }

View file

@ -93,7 +93,8 @@ export default async function (app: Express, db: NodePgDatabase) {
).map((e) => { ).map((e) => {
return { return {
token: e.token, token: e.token,
expires: now.to(dayjs(e.expires || 0)) expires: e.expires,
expiresString: now.to(dayjs(e.expires || 0))
}; };
}); });
const { codesUsed } = ( const { codesUsed } = (

View file

@ -5,9 +5,10 @@ import { count, desc, eq } from "drizzle-orm";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
export enum UserStatus { export enum UserStatus {
MODERATOR = 0b001, MODERATOR = 0b0001,
BANNED = 0b010, BANNED = 0b0010,
TRUSTED = 0b100 TRUSTED = 0b0100,
GUEST = 0b1000
} }
const nonceChars = const nonceChars =

View file

@ -103,17 +103,19 @@ summary {
} }
#page { #page {
position: relative;
height: calc(100% - 1.5em - 76px); height: calc(100% - 1.5em - 76px);
overflow-y: auto; overflow-y: auto;
} }
#sidebar { #sidebar {
padding: 1em; position: sticky;
top: 0;
float: left; float: left;
border-right: 1px solid #afa870;
padding: 1em;
width: 256px; width: 256px;
height: 100%; height: 100%;
margin-right: 24px;
border-right: 1px solid #afa870;
} }
#content { #content {

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

8
views/_util.pug Normal file
View file

@ -0,0 +1,8 @@
mixin invite_code_expiration(code)
- const timestamp = code.expires.getTime()
if timestamp === 0
td.subtle never
else if Date.now() >= timestamp
td.error EXPIRED
else
td= code.expiresString

View file

@ -1,7 +1,22 @@
extends site.pug extends site.pug
include _util.pug
block content block content
h1 Admin Panel h1 Admin Panel
p Don't Be Evil
h2 Users
table
tbody
tr
th UID
th Name
th Status
for user of userTable
tr
td!= user.id.toString()
td
a(href=`/users/${user.uname}`)= user.uname
td= user.status.toString(2).padStart(4, "0")
h2 Invite codes h2 Invite codes
form(action="/codes/delete", method="post") form(action="/codes/delete", method="post")
table table
@ -14,12 +29,7 @@ block content
- const timestamp = code.expires.getTime() - const timestamp = code.expires.getTime()
tr tr
td= code.token td= code.token
if timestamp === 0 +invite_code_expiration(code)
td.subtle never
else if Date.now() >= timestamp
td.error EXPIRED
else
td= code.expiresString
td td
a(href=`/users/${code.uname}`)= code.uname a(href=`/users/${code.uname}`)= code.uname
td td

View file

@ -1,4 +1,5 @@
extends site.pug extends site.pug
include _util.pug
block head block head
link(rel="stylesheet", href="/css/dashboard.css") link(rel="stylesheet", href="/css/dashboard.css")
@ -25,6 +26,8 @@ block content
a(href="#feed") Feed a(href="#feed") Feed
| / | /
a(href="#invite-codes") Invite codes a(href="#invite-codes") Invite codes
p This is where you "MIPILIN"! That is all you need to know!!!
p If onlookers notice your actions and inquire about what you are doing, you MUST tell them that you are mipilining all over the place, and then PROMPTLY SCROLL DOWN AND GENERATE AN INVITE CODE SO THAT THEY CAN MIPILIN TOO. If you do not do this I will cry.
form#dashboard-update-form(action="/update/mood", method="post", onsubmit="disable(this);") form#dashboard-update-form(action="/update/mood", method="post", onsubmit="disable(this);")
select(name="mood", required) select(name="mood", required)
//- Maybe put the index of the mood in the value of the option element //- Maybe put the index of the mood in the value of the option element
@ -53,7 +56,7 @@ block content
for code of codes for code of codes
tr tr
td= code.token td= code.token
td= code.expires +invite_code_expiration(code)
else else
.subtle You have no currently active invite codes. .subtle You have no currently active invite codes.
br br
@ -61,7 +64,9 @@ block content
button(type="submit", disabled=codesUsed>=5) Generate button(type="submit", disabled=codesUsed>=5) Generate
script(nonce=nonce). script(nonce=nonce).
function disable(form) { function disable(form) {
form.preventDefault();
const btn = form.querySelector("button"); const btn = form.querySelector("button");
btn.preventDefault();
btn.setAttribute("disabled", true); btn.setAttribute("disabled", true);
setTimeout(() => { setTimeout(() => {

View file

@ -33,7 +33,13 @@ block page
block content block content
p p
| Hi, this is mipilin! It lets you tell your friends how you're feeling, as well as keep a journal of your current mood and their trends over time! Due respect goes to imood, from which I borrowed many ideas and basically all of the moods. | Hi, this is mipilin (pronounced /mipilin/)! It lets you tell your friends how you're feeling, as well as keep a journal of your current mood and their trends over time! Due respect goes to
a(href="https://www.imood.com/") imood
|, from which I borrowed many ideas and basically all of the moods.
p
| mipilin is free and open source forevur. If you want to CHECK OUT TEH CODE or FILE AN ISSUE then you may do so at the respective links provided on the
a(href="https://sr.ht/~roxwize/mipilin") project page
| .
h1 Global Feed h1 Global Feed
p Look at how all these people are doing!!! p Look at how all these people are doing!!!
include _feed.pug include _feed.pug

View file

@ -7,6 +7,7 @@ block head
block content block content
h1 Your Journal h1 Your Journal
p This is where you can log your overall mood every day, and get a glimpse at how your life is going so far! p This is where you can log your overall mood every day, and get a glimpse at how your life is going so far!
p In the near future there will be a magnificient graph that will let you visualize your past entries and your mood trends. You must tay stuned.
form#journal-update(action="/update/journal", method="post") form#journal-update(action="/update/journal", method="post")
.input .input
span Overall mood change (how do you feel compared to yesterday?) span Overall mood change (how do you feel compared to yesterday?)

5
views/journal_edit.pug Normal file
View file

@ -0,0 +1,5 @@
extends site.pug
block content
p God i fucking hate myself
textarea(name="description", id="description", placeholder="max 4096 chars", maxlength="4096", cols="60", rows="12")