diff --git a/src/models/user.js b/src/models/user.js index cad2bd3..dbdc944 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -9,7 +9,8 @@ const UserSchema = new Schema({ username: { type: String, required: true, index: { unique: true } }, password: { type: String, required: true }, realname: { type: String, required: true }, - author: { type: Boolean, default: false } + author: { type: Boolean, default: false }, + avatar: { type: String, default: 'default.jpg' } }); diff --git a/src/routes/a/[slug].json.js b/src/routes/a/[slug].json.js index 48a706e..22b1771 100644 --- a/src/routes/a/[slug].json.js +++ b/src/routes/a/[slug].json.js @@ -6,14 +6,14 @@ export async function get(req, res, next) { const { slug } = req.params; const article = await Article.findOne({ slug }).populate({ path: 'author', - select: 'realname' + select: 'realname avatar' }).populate({ path: 'category' }); if (article) { article.set({ views: article.views + 1 }); - article.save(); + await article.save(); res.writeHead(200, { 'Content-Type': 'application/json' }); diff --git a/src/routes/a/[slug].svelte b/src/routes/a/[slug].svelte index 70fc58e..5d38884 100644 --- a/src/routes/a/[slug].svelte +++ b/src/routes/a/[slug].svelte @@ -82,6 +82,10 @@ margin: 0 0 0.5em 0; } + .content :global(img) { + max-width: 100%; + } + @media (min-width: 800px) { .content { width: 75vw; @@ -154,7 +158,7 @@

{article.title}

-

Author: {article.author.realname}

+

Author: {article.author.realname} {article.author.realname}

Category: {article.category.name}

Published: {new Date(article.created_at).toLocaleString()}

Views: {article.views}

@@ -168,6 +172,9 @@
{#each comments as comment}
+ {#if comment.author_user} + {comment.author_user.realname} + {/if}

{#if comment.author_user} {comment.author_user.realname} (verified) - {new Date(comment.created_at).toLocaleString()} diff --git a/src/routes/a/[slug]/comments.js b/src/routes/a/[slug]/comments.js index 8bd731c..f49329d 100644 --- a/src/routes/a/[slug]/comments.js +++ b/src/routes/a/[slug]/comments.js @@ -4,7 +4,7 @@ export async function get(req, res) { const { slug } = req.params; const article = await Article.findOne({ slug }).populate({ path: 'comments.author_user', - select: 'realname' + select: 'realname avatar' }); if (article) { res.writeHead(200, { @@ -25,7 +25,7 @@ export async function post(req, res) { const { slug } = req.params; let article = await Article.findOne({ slug }).populate({ path: 'comments.author_user', - select: 'realname' + select: 'realname avatar' }); if (article) { @@ -63,7 +63,7 @@ export async function post(req, res) { } else { article.comments.push({ author, content }); } - article.save(); + await article.save(); res.writeHead(200, { 'Content-Type': 'application/json' }); diff --git a/src/routes/cms/index.svelte b/src/routes/cms/index.svelte index 5df595c..8b5a522 100644 --- a/src/routes/cms/index.svelte +++ b/src/routes/cms/index.svelte @@ -24,6 +24,8 @@

Publish a new article

Edit an existing article Coming soon!

Delete an article

+

Account Settings

+

Change your avatar

{:else}

Welcome to your account. Contact the webmaster if your account needs publisher privileges.

{/if} diff --git a/src/routes/contact.svelte b/src/routes/contact.svelte index 7c3bf24..6662c93 100644 --- a/src/routes/contact.svelte +++ b/src/routes/contact.svelte @@ -17,7 +17,7 @@

Myles C. Linden

-

Creative Operations Director

+

Director of Financial Growth and Prosperity

FemboyFinancial Holdings Co., Ltd. (USA LLC)

1198 South 6th Street
diff --git a/src/routes/me/avatar.svelte b/src/routes/me/avatar.svelte new file mode 100644 index 0000000..ac4daf3 --- /dev/null +++ b/src/routes/me/avatar.svelte @@ -0,0 +1,70 @@ + + + + Change Avatar | HOWFEED.BIZ + + + + +
+ < Back to Dashboard +

Change Avatar

+

+ {$session.user.realname} + ← This is you, you ugly piece of shit. God, no wonder you're an incel. +

+
+

+ Change your photo so you at least have a chance: + + +

+
+

Or better yet, erase your wretched face from this site entirely:

+
diff --git a/src/server.js b/src/server.js index b586b68..c8aab96 100644 --- a/src/server.js +++ b/src/server.js @@ -10,6 +10,7 @@ import { Strategy } from 'passport-local'; import sessionFileStore from 'session-file-store'; import { RateLimiterMemory } from 'rate-limiter-flexible'; import fileUpload from 'express-fileupload'; +import fs from 'fs'; import helmet from 'helmet'; import crypto from 'crypto'; import Article from './models/article.js'; @@ -411,6 +412,93 @@ express() } }) + .post('/me/avatar', + async function(req, res, next) { + if (!req.user) { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You must be logged in to set an avatar.` + })); + return false; + } + try { + const { upload } = req.files; + if (!upload) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You must supply a file.` + })); + return false; + } + if (!/^image\//.test(upload.mimetype)) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `Invalid MIME type for the uploaded image.` + })); + return false; + } + if (upload.truncated) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `Received truncated image file. Try again with a smaller file.` + })); + return false; + } + const ext = upload.name.match(/(\.[^.]+)$/)[0]; + const filename = crypto.randomBytes(20).toString('hex') + ext; + const url = `/u/${filename}`; + await upload.mv('./static' + url); + const user = await User.findById(req.user._id); + req.user.avatar = user.avatar = filename; + await user.save(); + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ filename })); + } catch (err) { + res.writeHead(500, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `Failed to upload image: ${err}` + })); + } + } + ) + + .delete('/me/avatar', + async function(req, res, next) { + if (!req.user) { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You must be logged in to set an avatar.` + })); + return false; + } + const user = await User.findById(req.user._id); + const filename = 'default.jpg'; + if (user.avatar !== filename) { + fs.unlinkSync(`./static/u/${user.avatar}`); + } + req.user.avatar = user.avatar = filename; + await user.save(); + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ filename })); + } + ) + .use(compression({ threshold: 0 })) .use(sirv('./static', { dev })) .use(sapper.middleware({ diff --git a/static/global.css b/static/global.css index 54c6b90..78645a4 100644 --- a/static/global.css +++ b/static/global.css @@ -48,7 +48,7 @@ code { font-size: 2rem !important; } figure.article-image img { - height: 12rem !important; + height: 24rem !important; } div.article-list > a { margin: 2rem !important; @@ -83,7 +83,7 @@ figure.article-image { margin: 0; } figure.article-image img { - height: 6rem; + height: 12rem; width: auto; object-fit: contain; max-width: 100%; @@ -143,3 +143,9 @@ div.floaty { padding-top: 5rem !important; } } + +img.avatar { + height: 48px; + width: 48px; + object-fit: cover; +} diff --git a/static/u/.gitignore b/static/u/.gitignore new file mode 100644 index 0000000..6043b57 --- /dev/null +++ b/static/u/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!default.jpg diff --git a/static/u/default.jpg b/static/u/default.jpg new file mode 100644 index 0000000..2319858 Binary files /dev/null and b/static/u/default.jpg differ