mirror of
https://git.sr.ht/~roxwize/mipilin
synced 2025-01-30 18:53:36 +00:00
Some iprovements idfk
Signed-off-by: roxwize <rae@roxwize.xyz>
This commit is contained in:
parent
a3e6df6ce8
commit
961d963677
13 changed files with 160 additions and 102 deletions
7
TODO.md
7
TODO.md
|
@ -1,13 +1,14 @@
|
|||
- [ ] An audit log
|
||||
- [ ] 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
|
||||
- [ ] 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
|
||||
- [ ] Visibility indicator on journal entries
|
||||
- [ ] Hide journal entries from feed that are hidden unless current user is a moderator
|
||||
- [x] Visibility indicator on journal entries
|
||||
- [x] Hide journal entries from feed that are hidden unless current user is a moderator
|
||||
- [ ] Edit/delete journal entries?
|
||||
- [ ] Journal entry comments
|
||||
- [ ] A Forum
|
||||
- [ ] Make it easier to view journal entries
|
||||
|
|
4
main.ts
4
main.ts
|
@ -126,7 +126,7 @@ 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)), UserStatus.MODERATOR));
|
||||
console.log("This code expires in 1 minute and will confer admin powers to whoever signs up with it.");
|
||||
console.log(" " + await createInviteCode(db, 0, new Date(Date.now() + (10 * 1000 * 60)), UserStatus.MODERATOR));
|
||||
console.log("This code expires in 10 minutes and will confer admin powers to whoever signs up with it.");
|
||||
}
|
||||
}
|
||||
|
|
182
routes/admin.ts
182
routes/admin.ts
|
@ -5,96 +5,114 @@ import { inviteCodes, users } from "../db/schema.js";
|
|||
import { and, count, desc, eq, sql } from "drizzle-orm";
|
||||
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) {
|
||||
app.get("/mod", async (req, res) => {
|
||||
if (
|
||||
!req.session["loggedIn"] ||
|
||||
!(req.session["status"] & UserStatus.MODERATOR)
|
||||
) {
|
||||
render404(db, res, req);
|
||||
return;
|
||||
}
|
||||
app.get("/mod", async (req, res) => {
|
||||
if (
|
||||
!req.session["loggedIn"] ||
|
||||
!(req.session["status"] & UserStatus.MODERATOR)
|
||||
) {
|
||||
render404(db, res, req);
|
||||
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))
|
||||
};
|
||||
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 || 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) => {
|
||||
if (
|
||||
!req.session["loggedIn"] ||
|
||||
!(req.session["status"] & UserStatus.MODERATOR)
|
||||
) {
|
||||
res.redirect("/");
|
||||
return;
|
||||
}
|
||||
app.post("/codes/delete", async (req, res) => {
|
||||
if (
|
||||
!req.session["loggedIn"] ||
|
||||
!(req.session["status"] & UserStatus.MODERATOR)
|
||||
) {
|
||||
res.redirect("/");
|
||||
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
|
||||
.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];
|
||||
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;
|
||||
}
|
||||
.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
|
||||
.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];
|
||||
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));
|
||||
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 code = await createInviteCode(
|
||||
db,
|
||||
req.session["uid"],
|
||||
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);
|
||||
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);
|
||||
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");
|
||||
});
|
||||
req.flash("success", `Your code has been created as <b>${code}</b>.`);
|
||||
res.redirect("/mod");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -93,7 +93,8 @@ export default async function (app: Express, db: NodePgDatabase) {
|
|||
).map((e) => {
|
||||
return {
|
||||
token: e.token,
|
||||
expires: now.to(dayjs(e.expires || 0))
|
||||
expires: e.expires,
|
||||
expiresString: now.to(dayjs(e.expires || 0))
|
||||
};
|
||||
});
|
||||
const { codesUsed } = (
|
||||
|
|
|
@ -5,9 +5,10 @@ import { count, desc, eq } from "drizzle-orm";
|
|||
import fs from "node:fs/promises";
|
||||
|
||||
export enum UserStatus {
|
||||
MODERATOR = 0b001,
|
||||
BANNED = 0b010,
|
||||
TRUSTED = 0b100
|
||||
MODERATOR = 0b0001,
|
||||
BANNED = 0b0010,
|
||||
TRUSTED = 0b0100,
|
||||
GUEST = 0b1000
|
||||
}
|
||||
|
||||
const nonceChars =
|
||||
|
|
|
@ -103,17 +103,19 @@ summary {
|
|||
}
|
||||
|
||||
#page {
|
||||
position: relative;
|
||||
height: calc(100% - 1.5em - 76px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
padding: 1em;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
float: left;
|
||||
border-right: 1px solid #afa870;
|
||||
padding: 1em;
|
||||
width: 256px;
|
||||
height: 100%;
|
||||
margin-right: 24px;
|
||||
border-right: 1px solid #afa870;
|
||||
}
|
||||
|
||||
#content {
|
||||
|
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 167 KiB |
8
views/_util.pug
Normal file
8
views/_util.pug
Normal 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
|
|
@ -1,7 +1,22 @@
|
|||
extends site.pug
|
||||
include _util.pug
|
||||
|
||||
block content
|
||||
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
|
||||
form(action="/codes/delete", method="post")
|
||||
table
|
||||
|
@ -14,12 +29,7 @@ block content
|
|||
- 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
|
||||
+invite_code_expiration(code)
|
||||
td
|
||||
a(href=`/users/${code.uname}`)= code.uname
|
||||
td
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
extends site.pug
|
||||
include _util.pug
|
||||
|
||||
block head
|
||||
link(rel="stylesheet", href="/css/dashboard.css")
|
||||
|
@ -25,6 +26,8 @@ block content
|
|||
a(href="#feed") Feed
|
||||
| /
|
||||
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);")
|
||||
select(name="mood", required)
|
||||
//- Maybe put the index of the mood in the value of the option element
|
||||
|
@ -53,7 +56,7 @@ block content
|
|||
for code of codes
|
||||
tr
|
||||
td= code.token
|
||||
td= code.expires
|
||||
+invite_code_expiration(code)
|
||||
else
|
||||
.subtle You have no currently active invite codes.
|
||||
br
|
||||
|
@ -61,7 +64,9 @@ block content
|
|||
button(type="submit", disabled=codesUsed>=5) Generate
|
||||
script(nonce=nonce).
|
||||
function disable(form) {
|
||||
form.preventDefault();
|
||||
const btn = form.querySelector("button");
|
||||
btn.preventDefault();
|
||||
btn.setAttribute("disabled", true);
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -33,7 +33,13 @@ block page
|
|||
|
||||
block content
|
||||
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
|
||||
p Look at how all these people are doing!!!
|
||||
include _feed.pug
|
||||
|
|
|
@ -7,6 +7,7 @@ block head
|
|||
block content
|
||||
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 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")
|
||||
.input
|
||||
span Overall mood change (how do you feel compared to yesterday?)
|
||||
|
|
5
views/journal_edit.pug
Normal file
5
views/journal_edit.pug
Normal 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")
|
Loading…
Reference in a new issue