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:
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
|
- [ ] 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
|
||||||
|
|
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);
|
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 { 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");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 } = (
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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
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
|
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
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
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