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

mipilin r9... Fimally

Signed-off-by: roxwize <rae@roxwize.xyz>
This commit is contained in:
Rae 5e 2025-01-28 17:05:54 -05:00
parent 961d963677
commit fa8fae4638
Signed by: rae
GPG key ID: 5B1A0FAB9BAB81EE
9 changed files with 217 additions and 150 deletions

View file

@ -12,3 +12,5 @@
- [ ] Journal entry comments
- [ ] A Forum
- [ ] 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?)

View file

@ -68,6 +68,11 @@ export default function (app: Express, db: NodePgDatabase) {
return;
}
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 } = (
await db
.select({ codesUsed: count() })
@ -87,7 +92,7 @@ export default function (app: Express, db: NodePgDatabase) {
"error",
"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;
}
@ -100,19 +105,19 @@ export default function (app: Express, db: NodePgDatabase) {
"success",
`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;
}
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");
res.redirect(req.get("Referrer") || "/");
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");
res.redirect(req.get("Referrer") || "/");
});
}

View file

@ -2,7 +2,7 @@ import { Express } from "express";
import bcrypt from "bcrypt";
import { render } from "./util.js";
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";
//! TEMP Also not sanitized like at all
@ -21,6 +21,11 @@ export default function(app: Express, db: NodePgDatabase) {
return;
}
// 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) {
req.flash("error", "Invalid invite code! Make sure you pasted it in correctly WITH the hyphens.");
res.redirect("/register");
@ -36,7 +41,11 @@ export default function(app: Express, db: NodePgDatabase) {
res.redirect("/register");
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(
"error",
"Username can only contain letters, numbers, underscores, hyphens, and periods!!"
@ -101,6 +110,13 @@ export default function(app: Express, db: NodePgDatabase) {
)[0];
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["status"] = code.confers;
req.session["user"] = req.body.name;

View file

@ -112,6 +112,12 @@ export default async function (app: Express, db: NodePgDatabase) {
)
)[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, {
user,
moods,
@ -120,6 +126,8 @@ export default async function (app: Express, db: NodePgDatabase) {
recentUpdates,
codes,
codesUsed,
followed,
isTrusted: req.session["status"] & (UserStatus.MODERATOR | UserStatus.TRUSTED),
feed: []
});
});
@ -128,6 +136,15 @@ export default async function (app: Express, db: NodePgDatabase) {
res.redirect("/login");
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());
if (moodIndex === -1) {
req.flash(
@ -205,7 +222,8 @@ export default async function (app: Express, db: NodePgDatabase) {
if (
!entry ||
(entry.visibility === 0 &&
entry.uname !== req.session["user"] && !isMod)
entry.uname !== req.session["user"] &&
!isMod)
) {
render404(db, res, req);
return;
@ -247,16 +265,19 @@ export default async function (app: Express, db: NodePgDatabase) {
)[0];
const isMod = req.session["status"] & UserStatus.MODERATOR;
if (
!entry ||
(entry?.uid !== req.session["uid"] &&
!isMod)
) {
if (!entry || (entry?.uid !== req.session["uid"] && !isMod)) {
render404(db, res, req);
return;
}
if (isMod && entry.uid !== req.session["uid"] && req.body.action !== "delete") {
req.flash("error", "Moderators can only delete other users' posts.");
if (
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}`);
return;
}

View file

@ -149,7 +149,7 @@ export default async function (app: Express, db: NodePgDatabase) {
(uname || "") !== req.session["user"] &&
!(req.session["status"] & UserStatus.MODERATOR)
) {
res.redirect("back");
res.redirect(req.get("Referrer") || "/");
return;
}
@ -182,11 +182,7 @@ export default async function (app: Express, db: NodePgDatabase) {
.where(eq(users.name, req.params.user))
)[0];
if (!uid) {
req.flash(
"error",
"It looks like you're trying to follow a user that doesn't exist anymore."
);
res.redirect("/");
render404(db, res, req);
return;
}
const isFollowing = !!(
@ -217,6 +213,6 @@ export default async function (app: Express, db: NodePgDatabase) {
followerId: req.session["uid"]
});
}
res.redirect(`/users/${req.params.user}`);
res.redirect(req.get("Referrer") || "/");
});
}

View file

@ -1,231 +1,239 @@
* {
box-sizing: border-box;
box-sizing: border-box;
}
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
background-color: #21211d;
background-image: url("/img/bg.png");
background-size: cover;
width: 100vw;
height: 100vh;
font-family: "Gohufont 14", sans-serif;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
background-color: #21211d;
background-image: url("/img/bg.png");
background-size: cover;
width: 100vw;
height: 100vh;
font-family: "Gohufont 14", sans-serif;
font-size: 14px;
}
main {
background-color: #faf8da;
width: 95%;
max-width: 953px;
height: 95%;
background-color: #faf8da;
width: 95%;
max-width: 953px;
height: 95%;
}
a {
color: #4630df;
color: #4630df;
}
h1,
h2,
h3 {
margin: 0;
margin: 0;
}
header {
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: #e0d665;
background-image: linear-gradient(to bottom, #f6ef98, #c7c07d);
padding: 16px;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: #e0d665;
background-image: linear-gradient(to bottom, #f6ef98, #c7c07d);
padding: 16px;
width: 100%;
overflow: hidden;
}
#header-logo {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
height: 40px;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
height: 40px;
}
header nav {
display: flex;
align-items: center;
gap: 2em;
display: flex;
align-items: center;
gap: 2em;
}
header nav a {
text-decoration: none;
text-shadow: 0 0px 4px rgba(0, 0, 0, 0.8);
color: #faf8da;
font-size: 24px;
font-weight: bold;
text-decoration: none;
text-shadow: 0 0px 4px rgba(0, 0, 0, 0.8);
color: #faf8da;
font-size: 24px;
font-weight: bold;
}
header nav a:visited {
color: #faf8da;
color: #faf8da;
}
header nav a:hover {
text-decoration: underline;
text-decoration: underline;
}
summary {
cursor: pointer;
cursor: pointer;
}
#ticker {
position: relative;
border: 1px solid #25251d;
background-color: #454542;
height: 1.5em;
color: #faf8da;
position: relative;
border: 1px solid #25251d;
background-color: #454542;
height: 1.5em;
color: #faf8da;
}
#ticker > div {
display: flex;
float: left;
align-items: center;
justify-content: center;
border-right: 1px solid #25251d;
background-color: #3b3b33;
padding: 0 4px;
min-width: 255px;
height: 100%;
text-align: center;
display: flex;
float: left;
align-items: center;
justify-content: center;
border-right: 1px solid #25251d;
background-color: #3b3b33;
padding: 0 4px;
min-width: 255px;
height: 100%;
text-align: center;
}
#ticker div a {
color: #faf8da;
color: #faf8da;
}
#ticker #ticker-marquee {
width: auto;
height: 100%;
display: flex;
align-items: center;
width: auto;
height: 100%;
display: flex;
align-items: center;
}
#page {
position: relative;
height: calc(100% - 1.5em - 76px);
overflow-y: auto;
position: relative;
height: calc(100% - 1.5em - 76px);
overflow-y: auto;
}
#sidebar {
position: sticky;
top: 0;
float: left;
border-right: 1px solid #afa870;
padding: 1em;
width: 256px;
height: 100%;
position: sticky;
top: 0;
float: left;
border-right: 1px solid #afa870;
padding: 1em;
width: 256px;
height: 100%;
}
#content {
padding: 1em;
padding: 1em;
}
#content > *:nth-child(2) {
margin-top: 0;
margin-inline-start: 0;
margin-top: 0;
margin-inline-start: 0;
}
#content > *:last-child {
margin-bottom: 0;
margin-inline-end: 0;
margin-bottom: 0;
margin-inline-end: 0;
}
#flashes div {
margin-bottom: 8px;
padding: 8px;
border-width: 1px;
border-style: solid;
margin-bottom: 8px;
padding: 8px;
border-width: 1px;
border-style: solid;
}
#flashes div:first-child {
margin-top: 0;
margin-top: 0;
}
.flash-error {
background-color: #e06583;
border-color: #911533;
background-color: #e06583;
border-color: #911533;
}
.flash-info {
background-color: #8ca3f2;
border-color: #6371a1;
background-color: #8ca3f2;
border-color: #6371a1;
}
.flash-success {
background-color: #8cf2a6;
border-color: #3da758;
background-color: #8cf2a6;
border-color: #3da758;
}
.error {
color: #c51e48;
color: #c51e48;
}
.subtle {
opacity: 0.5;
opacity: 0.5;
}
button,
textarea,
select,
input {
border: 1px solid #757462;
background-color: white;
padding: 4px;
font-family: inherit;
font-size: 14px;
border: 1px solid #757462;
background-color: white;
padding: 4px;
font-family: inherit;
font-size: 14px;
}
button:focus,
textarea:focus,
select:focus,
input:focus {
outline: 0;
border: 1px solid #000000;
outline: 0;
border: 1px solid #000000;
}
button,
select {
cursor: pointer;
cursor: pointer;
}
table button {
padding: 0 4px;
padding: 0 4px;
}
th, td {
text-align: left;
padding-right: 1em;
th,
td {
text-align: left;
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 {
background-color: #d5d4bb;
height: fit-content;
word-wrap: break-word;
background-color: #d5d4bb;
height: fit-content;
word-wrap: break-word;
}
.feed-update div:first-child {
display: flex;
justify-content: space-between;
display: flex;
justify-content: space-between;
}
.feed-update div:last-child {
text-align: right;
color: #3f3f38;
font-style: italic;
text-align: right;
color: #3f3f38;
font-style: italic;
}
@font-face {
font-family: "Gohufont 14";
src: url("/fonts/gohufont-14.ttf") format("truetype"),
url("/fonts/gohufont-14.woff") format("woff");
font-display: swap;
font-family: "Gohufont 14";
src: url("/fonts/gohufont-14.ttf") format("truetype"),
url("/fonts/gohufont-14.woff") format("woff");
font-display: swap;
}
@media screen and (min-width: 900px) {
#feed {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.feed-update {
padding: 4px;
width: 280px;
}
#feed {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.feed-update {
padding: 4px;
width: 280px;
}
}
@media screen and (max-width: 900px) {
.feed-update {
border-bottom: 1px solid #afa870;
padding: 8px;
}
.feed-update {
border-bottom: 1px solid #afa870;
padding: 8px;
}
}

View file

@ -18,6 +18,7 @@ block content
a(href=`/users/${user.uname}`)= user.uname
td= user.status.toString(2).padStart(4, "0")
h2 Invite codes
p Well Rae. You've made quite a The Closed Beta.
form(action="/codes/delete", method="post")
table
tbody

View file

@ -15,11 +15,23 @@ block page
button(type="submit") Edit
h1(style="margin-top:1em;") Mood history
ul#dashboard-mood-history
if moodHistory.length < 1
span [no updates]
for mood of moodHistory
li
strong= mood.mood
|
| #{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
p
@ -42,7 +54,7 @@ block content
+feed(recentUpdates)
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
| Your current invite codes (
strong= codesUsed
@ -61,13 +73,15 @@ block content
.subtle You have no currently active invite codes.
br
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).
function disable(form) {
form.preventDefault();
const btn = form.querySelector("button");
btn.preventDefault();
btn.setAttribute("disabled", true);
btn.setAttribute("disabled", "");
setTimeout(() => {
btn.removeAttribute("disabled");

View file

@ -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
a(href="https://www.imood.com/") imood
|, 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
| 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