user avatars
This commit is contained in:
parent
e3f6fe0d2e
commit
060e7ae1e6
@ -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' }
|
||||
});
|
||||
|
||||
|
||||
|
@ -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'
|
||||
});
|
||||
|
@ -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 @@
|
||||
<div class="article-meta">
|
||||
<h1 class="article-title">{article.title}</h1>
|
||||
<blockquote>
|
||||
<p>Author: <strong>{article.author.realname}</strong></p>
|
||||
<p>Author: <img class="avatar" alt={article.author.realname} src={`/u/${article.author.avatar || 'default.jpg'}`}> <strong>{article.author.realname}</strong></p>
|
||||
<p>Category: <strong><a href={`/c/${article.category.slug}`}>{article.category.name}</a></strong></p>
|
||||
<p>Published: <strong>{new Date(article.created_at).toLocaleString()}</strong></p>
|
||||
<p>Views: <strong>{article.views}</strong></p>
|
||||
@ -168,6 +172,9 @@
|
||||
<div class="comments">
|
||||
{#each comments as comment}
|
||||
<div class="comment">
|
||||
{#if comment.author_user}
|
||||
<img class="avatar" alt={comment.author_user.realname} src={`/u/${comment.author_user.avatar || 'default.jpg'}`}>
|
||||
{/if}
|
||||
<p class="comment-meta">
|
||||
{#if comment.author_user}
|
||||
<span class="comment-username">{comment.author_user.realname}</span> <span class="comment-verified">(verified)</span> - {new Date(comment.created_at).toLocaleString()}
|
||||
|
@ -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'
|
||||
});
|
||||
|
@ -24,6 +24,8 @@
|
||||
<p><a href="/cms/create">Publish a new article</a></p>
|
||||
<p><strike><a href="/cms/update">Edit an existing article</a></strike> Coming soon!</p>
|
||||
<p><a href="/cms/delete">Delete an article</a></p>
|
||||
<h1>Account Settings</h1>
|
||||
<p><a href="/me/avatar">Change your avatar</a></p>
|
||||
{:else}
|
||||
<p>Welcome to your account. Contact the webmaster if your account needs publisher privileges.</p>
|
||||
{/if}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="people">
|
||||
<div>
|
||||
<h3>Myles C. Linden</h3>
|
||||
<p>Creative Operations Director</p>
|
||||
<p>Director of Financial Growth and Prosperity</p>
|
||||
<p>FemboyFinancial Holdings Co., Ltd. (USA LLC)</p>
|
||||
<address>
|
||||
1198 South 6th Street<br>
|
||||
|
70
src/routes/me/avatar.svelte
Normal file
70
src/routes/me/avatar.svelte
Normal file
@ -0,0 +1,70 @@
|
||||
<script context="module">
|
||||
export async function preload(page, session)
|
||||
{
|
||||
if (!session.user || !session.user.author) {
|
||||
return this.redirect(302, '/cms');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Change Avatar | HOWFEED.BIZ</title>
|
||||
</svelte:head>
|
||||
|
||||
<script>
|
||||
import { goto, stores } from '@sapper/app';
|
||||
const { session } = stores();
|
||||
|
||||
let uploadForm;
|
||||
|
||||
async function upload()
|
||||
{
|
||||
let fd = new FormData(uploadForm);
|
||||
const res = await fetch(`/me/avatar`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: fd
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.status === 200) {
|
||||
$session.user.avatar = json.filename;
|
||||
} else {
|
||||
alert(`Error ${res.status}: ${json.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function del()
|
||||
{
|
||||
const res = await fetch(`/me/avatar`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.status === 200) {
|
||||
$session.user.avatar = json.filename;
|
||||
} else {
|
||||
alert(`Error ${res.status}: ${json.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
<a href="/cms">< Back to Dashboard</a>
|
||||
<h1>Change Avatar</h1>
|
||||
<p>
|
||||
<img class="avatar" alt={$session.user.realname} src={`/u/${$session.user.avatar || 'default.jpg'}`}>
|
||||
← This is you, you ugly piece of shit. God, no wonder you're an incel.
|
||||
</p>
|
||||
<form bind:this={uploadForm} on:submit|preventDefault={upload}>
|
||||
<p>
|
||||
Change your photo so you at least have a chance:
|
||||
<input type="file" name="upload">
|
||||
<button type="submit">Upload</button>
|
||||
</p>
|
||||
</form>
|
||||
<p>Or better yet, erase your wretched face from this site entirely: <button on:click={del}>Reset Avatar</button></p>
|
||||
</div>
|
@ -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({
|
||||
|
@ -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;
|
||||
}
|
||||
|
3
static/u/.gitignore
vendored
Normal file
3
static/u/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!default.jpg
|
BIN
static/u/default.jpg
Normal file
BIN
static/u/default.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 KiB |
Loading…
x
Reference in New Issue
Block a user