#50 [card] pronoun cards - banner
This commit is contained in:
parent
9f252c10c1
commit
08f3f2a649
|
@ -3,20 +3,17 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import md5 from 'js-md5';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { gravatar } from "../src/helpers";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: { required: true },
|
||||
size: { 'default': 128 }
|
||||
},
|
||||
computed: {
|
||||
gravatar(email, size = 128) {
|
||||
const fallback = `https://avi.avris.it/${this.size}/${Base64.encode(this.user.username).replace(/\+/g, '-').replace(/\//g, '_')}.png`;
|
||||
|
||||
return `https://www.gravatar.com/avatar/${this.user.emailHash || md5(this.user.email)}?d=${encodeURIComponent(fallback)}&s=${this.size}`;
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
gravatar: gravatar(this.user, this.size),
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
head() {
|
||||
return head({
|
||||
title: `@${this.username}`,
|
||||
banner: `banner/@${this.username}.png`,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,37 +1,41 @@
|
|||
import { buildTemplate, parseTemplates } from "../src/buildTemplate";
|
||||
import { createCanvas, registerFont, loadImage } from 'canvas';
|
||||
import Papa from 'papaparse';
|
||||
import fs from 'fs';
|
||||
import { loadTsv } from './tsv';
|
||||
import translations from '../server/translations';
|
||||
import {gravatar, renderImage, renderText} from "../src/helpers";
|
||||
const dbConnection = require('./db');
|
||||
const SQL = require('sql-template-strings');
|
||||
|
||||
|
||||
const drawCircle = (context, image, x, y, size) => {
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2, true);
|
||||
context.closePath();
|
||||
context.clip();
|
||||
|
||||
context.drawImage(image, x, y, size, size)
|
||||
|
||||
context.beginPath();
|
||||
context.arc(x, y, size / 2, 0, Math.PI * 2, true);
|
||||
context.clip();
|
||||
context.closePath();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
const loadTsv = (filename) => {
|
||||
return Papa.parse(fs.readFileSync(__dirname + '/../data/' + filename).toString('utf-8'), {
|
||||
dynamicTyping: true,
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
}).data;
|
||||
};
|
||||
|
||||
export default async function (req, res, next) {
|
||||
if (req.url.substr(req.url.length - 4) !== '.png') {
|
||||
res.statusCode = 404;
|
||||
res.write('Not found');
|
||||
res.end();
|
||||
return;
|
||||
return renderText(res, 'Not found', 404);
|
||||
}
|
||||
|
||||
const templateName = decodeURIComponent(req.url.substr(1, req.url.length - 5));
|
||||
|
||||
const template = buildTemplate(
|
||||
parseTemplates(loadTsv('templates/templates.tsv')),
|
||||
templateName,
|
||||
);
|
||||
|
||||
const width = 1200
|
||||
const height = 600
|
||||
const mime = 'image/png';
|
||||
const imageSize = 200;
|
||||
const leftRatio = template || templateName === 'dowolne' ? 4 : 5;
|
||||
let leftRatio = 4;
|
||||
|
||||
registerFont('static/fonts/quicksand-v21-latin-ext_latin-regular.ttf', { family: 'Quicksand', weight: 'regular'});
|
||||
registerFont('static/fonts/quicksand-v21-latin-ext_latin-700.ttf', { family: 'Quicksand', weight: 'bold'});
|
||||
|
@ -43,22 +47,59 @@ export default async function (req, res, next) {
|
|||
context.fillRect(0, 0, width, height)
|
||||
context.fillStyle = '#000'
|
||||
|
||||
const image = await loadImage('node_modules/@fortawesome/fontawesome-pro/svgs/light/tags.svg');
|
||||
context.drawImage(image, width / leftRatio - imageSize / 2, height / 2 - imageSize / 1.25 / 2, imageSize, imageSize / 1.25)
|
||||
const fallback = async _ => {
|
||||
const logo = await loadImage('node_modules/@fortawesome/fontawesome-pro/svgs/light/tags.svg');
|
||||
leftRatio = 5;
|
||||
context.drawImage(logo, width / leftRatio - imageSize / 2, height / 2 - imageSize / 1.25 / 2, imageSize, imageSize / 1.25);
|
||||
context.font = 'regular 120pt Quicksand';
|
||||
context.fillText(translations.title, width / leftRatio + imageSize / 1.5, height / 2 + 48);
|
||||
}
|
||||
|
||||
if (template || templateName === 'dowolne') {
|
||||
if (templateName.startsWith('@')) {
|
||||
const db = await dbConnection();
|
||||
const user = await db.get(SQL`SELECT username, email FROM users WHERE username=${templateName.substring(1)}`);
|
||||
if (!user) {
|
||||
await fallback();
|
||||
return renderImage(res, canvas, mime);
|
||||
}
|
||||
|
||||
const avatar = await loadImage(gravatar(user, imageSize));
|
||||
|
||||
drawCircle(context, avatar, width / leftRatio - imageSize / 2, height / 2 - imageSize / 2, imageSize);
|
||||
|
||||
context.font = `regular 48pt Quicksand`
|
||||
context.fillText('@' + user.username, width / leftRatio + imageSize, height / 2)
|
||||
|
||||
const logo = await loadImage('static/favicon.svg');
|
||||
|
||||
context.font = 'regular 24pt Quicksand'
|
||||
context.fillStyle = '#C71585';
|
||||
const logoSize = 24 * 1.25;
|
||||
context.drawImage(logo, width / leftRatio + imageSize, height / 2 + logoSize - 4, logoSize, logoSize / 1.25)
|
||||
context.fillText(translations.title, width / leftRatio + imageSize + 36, height / 2 + 48);
|
||||
|
||||
return renderImage(res, canvas, mime);
|
||||
}
|
||||
|
||||
const template = buildTemplate(
|
||||
parseTemplates(loadTsv(__dirname + '/../data/templates/templates.tsv')),
|
||||
templateName,
|
||||
);
|
||||
|
||||
const logo = await loadImage('node_modules/@fortawesome/fontawesome-pro/svgs/light/tags.svg');
|
||||
|
||||
if (!template && templateName !== 'dowolne') {
|
||||
await fallback();
|
||||
return renderImage(res, canvas, mime);
|
||||
}
|
||||
|
||||
context.drawImage(logo, width / leftRatio - imageSize / 2, height / 2 - imageSize / 1.25 / 2, imageSize, imageSize / 1.25)
|
||||
context.font = 'regular 48pt Quicksand'
|
||||
context.fillText(translations.template.intro + ':', width / leftRatio + imageSize / 1.5, height / 2 - 36)
|
||||
|
||||
const templateNameOptions = templateName === 'dowolne' ? ['dowolne'] : template.nameOptions();
|
||||
context.font = `bold ${templateNameOptions.length <= 2 ? '70' : '36'}pt Quicksand`
|
||||
context.fillText(templateNameOptions.join('\n'), width / leftRatio + imageSize / 1.5, height / 2 + (templateNameOptions.length <= 2 ? 72 : 24))
|
||||
} else {
|
||||
context.font = 'regular 120pt Quicksand'
|
||||
context.fillText(translations.title, width / leftRatio + imageSize / 1.5, height / 2 + 48)
|
||||
}
|
||||
|
||||
res.setHeader('content-type', mime);
|
||||
res.write(canvas.toBuffer(mime));
|
||||
res.end()
|
||||
return renderImage(res, canvas, mime);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {renderJson} from "../src/helpers";
|
||||
|
||||
const dbConnection = require('./db');
|
||||
const SQL = require('sql-template-strings');
|
||||
import { ulid } from 'ulid'
|
||||
|
@ -60,14 +62,15 @@ export default async function (req, res, next) {
|
|||
const user = authenticate(req);
|
||||
const isAdmin = user && user.authenticated && user.roles === 'admin';
|
||||
|
||||
let result = {error: 'Not found'}
|
||||
if (req.method === 'GET' && req.url === '/all') {
|
||||
result = await db.all(`
|
||||
return renderJson(res, await db.all(`
|
||||
SELECT * FROM nouns
|
||||
${isAdmin ? '' : 'WHERE approved = 1'}
|
||||
ORDER BY approved, masc
|
||||
`);
|
||||
} else if (req.method === 'POST' && req.url === '/submit') {
|
||||
`));
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/submit') {
|
||||
if (isAdmin || !isTroll(req.body.data)) {
|
||||
const id = ulid()
|
||||
await db.get(SQL`
|
||||
|
@ -83,19 +86,23 @@ export default async function (req, res, next) {
|
|||
await approve(db, id);
|
||||
}
|
||||
}
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/approve/') && isAdmin) {
|
||||
await approve(db, getId(req.url));
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/hide/') && isAdmin) {
|
||||
await hide(db, getId(req.url));
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/remove/') && isAdmin) {
|
||||
await remove(db, getId(req.url));
|
||||
result = 'ok';
|
||||
return renderJson(res, 'ok');
|
||||
}
|
||||
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.write(JSON.stringify(result));
|
||||
res.end()
|
||||
if (req.method === 'POST' && req.url.startsWith('/approve/') && isAdmin) {
|
||||
await approve(db, getId(req.url));
|
||||
return renderJson(res, 'ok');
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url.startsWith('/hide/') && isAdmin) {
|
||||
await hide(db, getId(req.url));
|
||||
return renderJson(res, 'ok');
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url.startsWith('/remove/') && isAdmin) {
|
||||
await remove(db, getId(req.url));
|
||||
return renderJson(res, 'ok');
|
||||
}
|
||||
|
||||
return renderJson(res, {error: 'Not found'}, 404);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const dbConnection = require('./db');
|
||||
const SQL = require('sql-template-strings');
|
||||
import {buildDict, render} from "../src/helpers";
|
||||
import {buildDict, renderJson} from "../src/helpers";
|
||||
import { ulid } from 'ulid'
|
||||
import authenticate from './authenticate';
|
||||
import md5 from 'js-md5';
|
||||
|
@ -50,7 +50,7 @@ export default async function (req, res, next) {
|
|||
AND profiles.active = 1
|
||||
ORDER BY profiles.locale
|
||||
`)
|
||||
return render(res, buildDict(function* () {
|
||||
return renderJson(res, buildDict(function* () {
|
||||
for (let profile of profiles) {
|
||||
yield [profile.locale, buildProfile(profile)];
|
||||
}
|
||||
|
@ -58,8 +58,8 @@ export default async function (req, res, next) {
|
|||
}
|
||||
|
||||
if (!user || !user.authenticated) {
|
||||
return render(res, {error: 'unauthorised'}, 401);
|
||||
return renderJson(res, {error: 'unauthorised'}, 401);
|
||||
}
|
||||
|
||||
return render(res, { error: 'notfound' }, 404);
|
||||
return renderJson(res, { error: 'notfound' }, 404);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import Papa from 'papaparse';
|
||||
import fs from "fs";
|
||||
|
||||
export const loadTsv = (filename) => {
|
||||
return Papa.parse(fs.readFileSync(filename).toString('utf-8'), {
|
||||
dynamicTyping: true,
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
}).data;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import jwt from './jwt';
|
||||
import { makeId } from '../src/helpers';
|
||||
import {makeId, renderJson} from '../src/helpers';
|
||||
const dbConnection = require('./db');
|
||||
const SQL = require('sql-template-strings');
|
||||
import { ulid } from 'ulid';
|
||||
|
@ -165,17 +165,17 @@ export default async function (req, res, next) {
|
|||
const db = await dbConnection();
|
||||
const user = authenticate(req);
|
||||
|
||||
let result = {error: 'notfound'}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/init' && req.body.usernameOrEmail) {
|
||||
result = await init(db, req.body.usernameOrEmail)
|
||||
} else if (req.method === 'POST' && req.url === '/validate' && req.body.code) {
|
||||
result = await validate(db, user, req.body.code);
|
||||
} else if (req.method === 'POST' && req.url === '/change-username' && user && user.authenticated && req.body.username) {
|
||||
result = await changeUsername(db, user, req.body.username);
|
||||
return renderJson(await init(db, req.body.usernameOrEmail));
|
||||
}
|
||||
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.write(JSON.stringify(result));
|
||||
res.end();
|
||||
if (req.method === 'POST' && req.url === '/validate' && req.body.code) {
|
||||
return renderJson(await validate(db, user, req.body.code));
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/change-username' && user && user.authenticated && req.body.username) {
|
||||
return renderJson(await changeUsername(db, user, req.body.username));
|
||||
}
|
||||
|
||||
return renderJson(res, {error: 'Not found'}, 404);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import md5 from 'js-md5';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export const buildDict = (fn, ...args) => {
|
||||
const dict = {};
|
||||
for (let [key, value] of fn(...args)) {
|
||||
|
@ -83,9 +86,29 @@ export const parseQuery = (queryString) => {
|
|||
return query;
|
||||
}
|
||||
|
||||
export const render = (res, content, status = 200) => {
|
||||
res.status = status;
|
||||
export const renderText = (res, content, status = 200) => {
|
||||
res.statusCode = status;
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.write(JSON.stringify(content));
|
||||
res.end();
|
||||
}
|
||||
|
||||
export const renderJson = (res, content, status = 200) => {
|
||||
res.statusCode = status;
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.write(JSON.stringify(content));
|
||||
res.end();
|
||||
}
|
||||
|
||||
export const renderImage = (res, canvas, mime, status = 200) => {
|
||||
res.statusCode = status;
|
||||
res.setHeader('content-type', mime);
|
||||
res.write(canvas.toBuffer(mime));
|
||||
res.end();
|
||||
}
|
||||
|
||||
export const gravatar = (user, size = 128) => {
|
||||
const fallback = `https://avi.avris.it/${size}/${Base64.encode(user.username).replace(/\+/g, '-').replace(/\//g, '_')}.png`;
|
||||
|
||||
return `https://www.gravatar.com/avatar/${user.emailHash || md5(user.email)}?d=${encodeURIComponent(fallback)}&s=${size}`;
|
||||
}
|
||||
|
|
Reference in New Issue