mirror of
https://git.sr.ht/~roxwize/mipilin
synced 2025-01-31 02:53:36 +00:00
mipilin r9... Fimally
Signed-off-by: roxwize <rae@roxwize.xyz>
This commit is contained in:
parent
961d963677
commit
fa8fae4638
9 changed files with 217 additions and 150 deletions
2
TODO.md
2
TODO.md
|
@ -12,3 +12,5 @@
|
||||||
- [ ] Journal entry comments
|
- [ ] Journal entry comments
|
||||||
- [ ] A Forum
|
- [ ] A Forum
|
||||||
- [ ] Make it easier to view journal entries
|
- [ ] Make it easier to view journal entries
|
||||||
|
- [x] Users can fuck with invite codes by using an invite code and then making an account with it and then having more invite codes SO MAKE IT The case that only users with the trusted status may Do that
|
||||||
|
- [ ] Private journals are stored in plaintext and can be found if a database breach happens, maybe (optionally?) encrypt them (with PGP keys?)
|
||||||
|
|
|
@ -68,6 +68,11 @@ export default function (app: Express, db: NodePgDatabase) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(req.session["status"] & UserStatus.MODERATOR)) {
|
if (!(req.session["status"] & UserStatus.MODERATOR)) {
|
||||||
|
if (!(req.session["status"] & UserStatus.TRUSTED)) {
|
||||||
|
req.flash("error", "Only trusted users can perform this action.");
|
||||||
|
res.redirect(req.get("Referrer") || "/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { codesUsed } = (
|
const { codesUsed } = (
|
||||||
await db
|
await db
|
||||||
.select({ codesUsed: count() })
|
.select({ codesUsed: count() })
|
||||||
|
@ -87,7 +92,7 @@ export default function (app: Express, db: NodePgDatabase) {
|
||||||
"error",
|
"error",
|
||||||
"You've generated the maximum of five codes this week. Your counter will reset next month."
|
"You've generated the maximum of five codes this week. Your counter will reset next month."
|
||||||
);
|
);
|
||||||
res.redirect("/dashboard");
|
res.redirect(req.get("Referrer") || "/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,19 +105,19 @@ export default function (app: Express, db: NodePgDatabase) {
|
||||||
"success",
|
"success",
|
||||||
`Your code has been created as <b>${code}</b>. It expires in a week so use it ASAP!!!`
|
`Your code has been created as <b>${code}</b>. It expires in a week so use it ASAP!!!`
|
||||||
);
|
);
|
||||||
res.redirect("/dashboard");
|
res.redirect(req.get("Referrer") || "/");
|
||||||
return;
|
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(req.get("Referrer") || "/");
|
||||||
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(req.get("Referrer") || "/");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { inviteCodes, profiles, users } from "../db/schema.js";
|
import { follows, 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
|
||||||
|
@ -21,6 +21,11 @@ export default function(app: Express, db: NodePgDatabase) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// validation
|
// validation
|
||||||
|
if (!req.body.name || !req.body.referral || !req.body.email || !req.body.pass) {
|
||||||
|
req.flash("error", "A required field wasn't filled in.");
|
||||||
|
res.redirect("/register");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (req.body.referral.length < 22) {
|
if (req.body.referral.length < 22) {
|
||||||
req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
|
req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
|
||||||
res.redirect("/register");
|
res.redirect("/register");
|
||||||
|
@ -36,7 +41,11 @@ export default function(app: Express, db: NodePgDatabase) {
|
||||||
res.redirect("/register");
|
res.redirect("/register");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!req.body.name.match(/[A-Z0-9_-]/i)) {
|
|
||||||
|
//! dumb
|
||||||
|
req.body.name = req.body.name.trim();
|
||||||
|
const match = req.body.name.match(/[A-Z0-9_-]+/i);
|
||||||
|
if (match?.[0] !== req.body.name) {
|
||||||
req.flash(
|
req.flash(
|
||||||
"error",
|
"error",
|
||||||
"Username can only contain letters, numbers, underscores, hyphens, and periods!!"
|
"Username can only contain letters, numbers, underscores, hyphens, and periods!!"
|
||||||
|
@ -101,6 +110,13 @@ export default function(app: Express, db: NodePgDatabase) {
|
||||||
)[0];
|
)[0];
|
||||||
await db.insert(profiles).values({ user: uid });
|
await db.insert(profiles).values({ user: uid });
|
||||||
|
|
||||||
|
// Follow me by default ;w;;;
|
||||||
|
//! Also this assumes that im at id 1 which might not be true ever
|
||||||
|
await db.insert(follows).values({
|
||||||
|
userId: 1,
|
||||||
|
followerId: uid
|
||||||
|
});
|
||||||
|
|
||||||
req.session["loggedIn"] = true;
|
req.session["loggedIn"] = true;
|
||||||
req.session["status"] = code.confers;
|
req.session["status"] = code.confers;
|
||||||
req.session["user"] = req.body.name;
|
req.session["user"] = req.body.name;
|
||||||
|
|
|
@ -112,6 +112,12 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
)
|
)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
|
const followed = await db
|
||||||
|
.select({ uname: users.name })
|
||||||
|
.from(follows)
|
||||||
|
.where(eq(follows.followerId, req.session["uid"]))
|
||||||
|
.innerJoin(users, eq(follows.userId, users.id));
|
||||||
|
|
||||||
render(db, "dashboard", "dashboard", res, req, {
|
render(db, "dashboard", "dashboard", res, req, {
|
||||||
user,
|
user,
|
||||||
moods,
|
moods,
|
||||||
|
@ -120,6 +126,8 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
recentUpdates,
|
recentUpdates,
|
||||||
codes,
|
codes,
|
||||||
codesUsed,
|
codesUsed,
|
||||||
|
followed,
|
||||||
|
isTrusted: req.session["status"] & (UserStatus.MODERATOR | UserStatus.TRUSTED),
|
||||||
feed: []
|
feed: []
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -128,6 +136,15 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
res.redirect("/login");
|
res.redirect("/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// make sure the user isnt updating too fast
|
||||||
|
//! TODO: also do this for journal entries
|
||||||
|
const lastUpdate = (await db.select({ date: updates.date }).from(updates).where(eq(updates.user, req.session["uid"])).orderBy(desc(updates.date)).limit(1))?.[0];
|
||||||
|
if (Date.now() < lastUpdate?.date?.getTime() + 10 * 1000) {
|
||||||
|
req.flash("error", "You're updating your mood too fast! Wait ten seconds between updates.");
|
||||||
|
res.redirect(req.get("Referrer") || "/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const moodIndex = moods.indexOf(req.body.mood.trim());
|
const moodIndex = moods.indexOf(req.body.mood.trim());
|
||||||
if (moodIndex === -1) {
|
if (moodIndex === -1) {
|
||||||
req.flash(
|
req.flash(
|
||||||
|
@ -205,7 +222,8 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
if (
|
if (
|
||||||
!entry ||
|
!entry ||
|
||||||
(entry.visibility === 0 &&
|
(entry.visibility === 0 &&
|
||||||
entry.uname !== req.session["user"] && !isMod)
|
entry.uname !== req.session["user"] &&
|
||||||
|
!isMod)
|
||||||
) {
|
) {
|
||||||
render404(db, res, req);
|
render404(db, res, req);
|
||||||
return;
|
return;
|
||||||
|
@ -247,16 +265,19 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
const isMod = req.session["status"] & UserStatus.MODERATOR;
|
const isMod = req.session["status"] & UserStatus.MODERATOR;
|
||||||
if (
|
if (!entry || (entry?.uid !== req.session["uid"] && !isMod)) {
|
||||||
!entry ||
|
|
||||||
(entry?.uid !== req.session["uid"] &&
|
|
||||||
!isMod)
|
|
||||||
) {
|
|
||||||
render404(db, res, req);
|
render404(db, res, req);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isMod && entry.uid !== req.session["uid"] && req.body.action !== "delete") {
|
if (
|
||||||
req.flash("error", "Moderators can only delete other users' posts.");
|
isMod &&
|
||||||
|
entry.uid !== req.session["uid"] &&
|
||||||
|
req.body.action !== "delete"
|
||||||
|
) {
|
||||||
|
req.flash(
|
||||||
|
"error",
|
||||||
|
"Moderators can only delete other users' posts."
|
||||||
|
);
|
||||||
res.redirect(`/journal/${req.params.id}`);
|
res.redirect(`/journal/${req.params.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
(uname || "") !== req.session["user"] &&
|
(uname || "") !== req.session["user"] &&
|
||||||
!(req.session["status"] & UserStatus.MODERATOR)
|
!(req.session["status"] & UserStatus.MODERATOR)
|
||||||
) {
|
) {
|
||||||
res.redirect("back");
|
res.redirect(req.get("Referrer") || "/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,11 +182,7 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
.where(eq(users.name, req.params.user))
|
.where(eq(users.name, req.params.user))
|
||||||
)[0];
|
)[0];
|
||||||
if (!uid) {
|
if (!uid) {
|
||||||
req.flash(
|
render404(db, res, req);
|
||||||
"error",
|
|
||||||
"It looks like you're trying to follow a user that doesn't exist anymore."
|
|
||||||
);
|
|
||||||
res.redirect("/");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isFollowing = !!(
|
const isFollowing = !!(
|
||||||
|
@ -217,6 +213,6 @@ export default async function (app: Express, db: NodePgDatabase) {
|
||||||
followerId: req.session["uid"]
|
followerId: req.session["uid"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.redirect(`/users/${req.params.user}`);
|
res.redirect(req.get("Referrer") || "/");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,10 +185,18 @@ select {
|
||||||
table button {
|
table button {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
tr:nth-child(odd) td {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
tr:hover td {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.feed-update {
|
.feed-update {
|
||||||
background-color: #d5d4bb;
|
background-color: #d5d4bb;
|
||||||
|
|
|
@ -18,6 +18,7 @@ block content
|
||||||
a(href=`/users/${user.uname}`)= user.uname
|
a(href=`/users/${user.uname}`)= user.uname
|
||||||
td= user.status.toString(2).padStart(4, "0")
|
td= user.status.toString(2).padStart(4, "0")
|
||||||
h2 Invite codes
|
h2 Invite codes
|
||||||
|
p Well Rae. You've made quite a The Closed Beta.
|
||||||
form(action="/codes/delete", method="post")
|
form(action="/codes/delete", method="post")
|
||||||
table
|
table
|
||||||
tbody
|
tbody
|
||||||
|
|
|
@ -15,11 +15,23 @@ block page
|
||||||
button(type="submit") Edit
|
button(type="submit") Edit
|
||||||
h1(style="margin-top:1em;") Mood history
|
h1(style="margin-top:1em;") Mood history
|
||||||
ul#dashboard-mood-history
|
ul#dashboard-mood-history
|
||||||
|
if moodHistory.length < 1
|
||||||
|
span [no updates]
|
||||||
for mood of moodHistory
|
for mood of moodHistory
|
||||||
li
|
li
|
||||||
strong= mood.mood
|
strong= mood.mood
|
||||||
|
|
|
|
||||||
| #{mood.date}
|
| #{mood.date}
|
||||||
|
h1(style="margin-top:1em;") Follows
|
||||||
|
table
|
||||||
|
tbody
|
||||||
|
for follow of followed
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
a(href=`/users/${follow.uname}`)= follow.uname
|
||||||
|
td
|
||||||
|
form(action=`/users/${follow.uname}/follow`, method="post")
|
||||||
|
button Unfollow
|
||||||
|
|
||||||
block content
|
block content
|
||||||
p
|
p
|
||||||
|
@ -42,7 +54,7 @@ block content
|
||||||
+feed(recentUpdates)
|
+feed(recentUpdates)
|
||||||
|
|
||||||
h1#invite-codes(style="margin-top:1em;") Invite codes
|
h1#invite-codes(style="margin-top:1em;") Invite codes
|
||||||
p Invite your friends to the mipilin beta! You can create up to five invite codes every month, and they all expire within a week.
|
p If you're a trusted user, you can invite your friends to the mipilin beta! You can create up to five invite codes every month, and they all expire within a week.
|
||||||
p
|
p
|
||||||
| Your current invite codes (
|
| Your current invite codes (
|
||||||
strong= codesUsed
|
strong= codesUsed
|
||||||
|
@ -61,13 +73,15 @@ block content
|
||||||
.subtle You have no currently active invite codes.
|
.subtle You have no currently active invite codes.
|
||||||
br
|
br
|
||||||
form(action="/codes/create", method="post")
|
form(action="/codes/create", method="post")
|
||||||
button(type="submit", disabled=codesUsed>=5) Generate
|
button(type="submit", disabled=codesUsed>=5 || !isTrusted) Generate
|
||||||
|
if codesUsed >= 5
|
||||||
|
p You've generated the maximum amount of codes this month.
|
||||||
|
if !isTrusted
|
||||||
|
p You need be manually marked as "trusted" to generate codes.
|
||||||
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", "");
|
||||||
btn.setAttribute("disabled", true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.removeAttribute("disabled");
|
btn.removeAttribute("disabled");
|
||||||
|
|
|
@ -36,6 +36,10 @@ block content
|
||||||
| 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
|
| 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
|
a(href="https://www.imood.com/") imood
|
||||||
|, from which I borrowed many ideas and basically all of the moods.
|
|, from which I borrowed many ideas and basically all of the moods.
|
||||||
|
p
|
||||||
|
| If you're wondering how to get started, look down. There is a lot of people! Also on the sidebar to your left is important information, including another list of users. Click on them! Follow them! Interact with them! Following someone will make their updates appear on your
|
||||||
|
a(href="/dashboard/#feed") dashboard's local feed
|
||||||
|
| . No one likes an empty local feed!!
|
||||||
p
|
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
|
| 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
|
a(href="https://sr.ht/~roxwize/mipilin") project page
|
||||||
|
|
Loading…
Reference in a new issue