tinypin/server.js
2021-01-21 14:12:16 -06:00

332 lines
No EOL
9.9 KiB
JavaScript

const express = require('express');
const bodyParser = require('body-parser');
const db = require('better-sqlite3')('data.db') //, {verbose:console.log});
const http = require('http');
const https = require('https');
const sharp = require('sharp');
const fs = require('fs').promises;
const fetch = require('node-fetch');
const IMAGE_PATH = "./images";
// express config
const app = express();
const port = 3000;
app.use(express.static('static'));
app.use(express.static('images'));
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
app.set('json spaces', 2);
const OK = {status: "ok"};
const NOT_FOUND = {status: "error", error: "not found"};
const ALREADY_EXISTS = {status: "error", error: "already exists"};
const SERVER_ERROR = {status: "error", error: "server error"};
initDb();
// list boards
app.get("/api/boards", (req, res) => {
try{
let boards = db.prepare("SELECT * FROM boards").all();
for( let i = 0; i < boards.length; ++i ){
let result = db.prepare("SELECT id FROM pins WHERE boardId = ? order by createDate limit 1").get(boards[i].id);
if ( result ) {
boards[i].titlePinId = result.id;
} else {
boards[i].titlePinId = 0;
}
}
res.send(boards);
} catch (err) {
console.log(`Error listing boards: ${err.message}`);
res.status(500).send(SERVER_ERROR);
}
});
// get board
app.get("/api/boards/:boardId", (req, res) => {
try{
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(req.params.boardId);
if ( board ){
board.pins = db.prepare("SELECT * FROM pins WHERE boardId = ?").all(req.params.boardId);
res.send(board);
} else {
res.status(404).send(NOT_FOUND);
}
} catch (err) {
console.log(`Error getting board#${req.params.boardId}: ${err.message}`);
res.status(500).send(SERVER_ERROR);
}
});
// create board
app.post('/api/boards', (req, res) => {
try{
let result = db.prepare("INSERT INTO boards (name, createDate) VALUES (@name, @createDate)").run({name: req.body.name, createDate: new Date().toISOString()});
let id = result.lastInsertRowid;
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(id);
res.send(board);
console.log(`Created board#${id} ${req.body.name}`);
} catch (err){
console.log("Error creating board: " + err.message);
if ( err.message.includes('UNIQUE constraint failed:') ){
res.status(409).send(ALREADY_EXISTS);
} else {
res.status(500).send(SERVER_ERROR);
}
}
});
// update board
app.post("/api/boards/:boardId", (req, res) =>{
try{
let result = db.prepare("UPDATE boards SET name = @name WHERE id = @boardId").run({name: req.body.name, boardId: req.params.boardId});
if ( result.changes == 1 ){
res.send(OK);
} else {
res.status(404).send(NOT_FOUND);
}
} catch (err){
console.log(`Error updating board#${req.params.boardId}: ${err.message}`);
res.status(500).send(SERVER_ERROR);
}
});
// delete board
app.delete("/api/boards/:boardId", (req, res) => {
try{
let result = db.prepare("DELETE FROM boards WHERE id = ?").run(req.params.boardId);
if ( result.changes == 1 ){
res.send(OK);
} else {
res.status(404).send(NOT_FOUND);
}
} catch (err) {
console.log(`Error deleting board#${req.params.boardId}: ${err.message}`);
res.status(500).send(SERVER_ERROR);
}
});
// get pin
app.get("/api/pins/:pinId", (req, res) => {
try {
let pin = db.prepare('SELECT * FROM pins WHERE id = ?').get(req.params.pinId);
if ( pin ){
res.send(pin);
} else {
res.status(404).send(NOT_FOUND);
}
} catch (err){
console.error(`Error getting pin#${req.params.pinId}: ${err.message}`, err);
res.status(500).send(SERVER_ERROR);
}
});
// create pin
app.post("/api/pins", async (req, res) => {
try {
console.log(req.body);
let image = await downloadImage(req.body.imageUrl);
console.log(image);
let result = db.prepare(`INSERT INTO PINS (
boardId,
imageUrl,
siteUrl,
description,
sortOrder,
originalHeight,
originalWidth,
thumbnailHeight,
thumbnailWidth,
createDate
) VALUES (
@boardId,
@imageUrl,
@siteUrl,
@description,
@sortOrder,
@originalHeight,
@originalWidth,
@thumbnailHeight,
@thumbnailWidth,
@createDate)
`).run({
boardId: req.body.boardId,
imageUrl: req.body.imageUrl,
siteUrl: req.body.siteUrl,
description: req.body.description,
sortOrder: req.body.sortOrder,
originalHeight: image.original.height,
originalWidth: image.original.width,
thumbnailHeight: image.thumbnail.height,
thumbnailWidth: image.thumbnail.width,
createDate: new Date().toISOString()
});
let id = result.lastInsertRowid;
// write the images to disk
let originalImagePath = getOriginalImagePath(id);
let thumbnailImagePath = getThumbnailImagePath(id);
await fs.mkdir(originalImagePath.dir, {recursive: true});
await fs.mkdir(thumbnailImagePath.dir, {recursive: true});
await fs.writeFile(originalImagePath.file, image.original.buffer);
console.log(`Saved original to: ${originalImagePath.file}`);
await fs.writeFile(thumbnailImagePath.file, image.thumbnail.buffer);
console.log(`Saved thumbnail to: ${thumbnailImagePath.file}`);
// return the newly created row
let pin = db.prepare("SELECT * FROM pins WHERE id = ?").get(id);
res.send(pin);
} catch (err) {
console.log(`Error creating pin: ${err.message}`, err);
res.status(500).send(SERVER_ERROR);
}
});
app.post("/api/pins/:pinId", (req,res) => {
try {
let result = db.prepare(`UPDATE pins SET
boardId = @boardId,
siteUrl = @siteUrl,
description = @description,
sortOrder = @sortOrder
WHERE id = @pinId
`).run({
pinId: req.params.pinId,
boardId: req.body.boardId,
siteUrl: req.body.siteUrl,
description: req.body.description,
sortOrder: req.body.sortOrder
});
if ( result.changes == 1 ){
res.send(OK);
} else {
res.status(404).send(NOT_FOUND);
}
} catch (err) {
console.log(`Error updating pin#${req.params.pinId}`, err);
res.status(500).send(SERVER_ERROR);
}
});
// start listening
app.listen(port, () => {
console.log(`tinypin is running at http://localhost:${port}`)
});
function initDb(){
db.prepare(`
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY,
createDate TEXT
)
`).run();
let schemaVersion = db.prepare('select max(id) as id from migrations').get().id;
if ( !schemaVersion || schemaVersion < 1 ){
console.log("Running migration to version 1");
db.prepare(`
CREATE TABLE IF NOT EXISTS boards (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
createDate TEXT)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS pins (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
boardId INTEGER NOT NULL,
imageUrl TEXT,
siteUrl TEXT,
description TEXT,
sortOrder INTEGER,
originalHeight INTEGER,
originalWidth INTEGER,
thumbnailHeight INTEGER,
thumbnailWidth INTEGER,
createDate TEXT,
FOREIGN KEY (boardId) REFERENCES boards(id)
)
`).run();
db.prepare("INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )").run({id:1, createDate: new Date().toISOString()});
} else {
console.log("Database schema v" + schemaVersion + " is up to date.");
}
}
async function downloadImage(imageUrl){
let res = await fetch(imageUrl);
if ( res.status != 200 ){
throw(new Error(`download error status=${res.status}`));
}
let buffer = await res.buffer();
let original = sharp(buffer);
let originalMetadata = await original.metadata();
let originalBuffer = await original.toFormat("jpg").toBuffer();
let thumbnail = await original.resize({ width: 400, height: 400, fit: 'inside' });
let thumbnailBuffer = await thumbnail.toBuffer();
let thumbnailMetadata = await sharp(thumbnailBuffer).metadata();
return {
original: {
buffer: originalBuffer,
width: originalMetadata.width,
height: originalMetadata.height
},
thumbnail: {
buffer: thumbnailBuffer,
width: thumbnailMetadata.width,
height: thumbnailMetadata.height
}
}
}
// function padId(id){
// let result = id.toString();
// while ( result.length < 12 ) {
// result = '0' + result;
// }
// return result;
// }
function getOriginalImagePath(pinId){
let paddedId = pinId.toString().padStart(12, '0');
let dir = `${IMAGE_PATH}/originals/${paddedId[11]}/${paddedId[10]}/${paddedId[9]}/${paddedId[8]}`;
let file = `${dir}/${paddedId}.jpg`;
return {dir: dir, file: file};
}
function getThumbnailImagePath(pinId){
let paddedId = pinId.toString().padStart(12, '0');
let dir = `${IMAGE_PATH}/thumbnails/${paddedId[11]}/${paddedId[10]}/${paddedId[9]}/${paddedId[8]}`;
let file = `${dir}/${paddedId}.jpg`;
return {dir: dir, file: file};
}