├── .gitignore ├── app ├── infra │ ├── async-wrap.js │ ├── index.js │ ├── auth.js │ ├── user-dao.js │ ├── comment-dao.js │ └── photo-dao.js ├── api │ ├── index.js │ ├── comment.js │ ├── user.js │ └── photo.js └── routes │ ├── index.js │ ├── comment.js │ ├── user.js │ └── photo.js ├── uploads └── imgs │ ├── 17f8c52c-8ca1-4809-b3b6-215137bdd182.jpg │ ├── 187baf2d-210c-4c1d-8867-277a8c0d3056.jpg │ ├── 2256466d-1f79-4913-bf8b-b8183e188bcf.jpg │ ├── 290c3726-c126-46aa-8ac6-e6ced81b0793.jpg │ ├── 39f424e2-6204-4dee-bed1-6d480543de5c.jpg │ ├── 3b2014e2-f9cc-467a-9b4d-aed22828bd66.jpg │ ├── 4a757683-63e0-44b3-8830-7d7793d875ac.jpg │ ├── 52cfee2d-d3bd-4b94-ba64-ff47d9d4a426.jpg │ ├── 5e9b04ad-8f17-4ee0-a314-083e0b9b4395.jpg │ ├── 89ae1cf3-4ff1-4713-bd7e-d4a8c8d4e0f9.jpg │ ├── 96ea350f-53d6-4bd3-9721-10d186ed5867.jpg │ ├── a382781e-e0f6-4887-83c3-c3f3595695b8.jpg │ ├── d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg │ ├── e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg │ └── f5918cea-a22b-470b-9053-c863b2a4a1e3.jpg ├── server.js ├── package.json └── config ├── express.js └── database.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /app/infra/async-wrap.js: -------------------------------------------------------------------------------- 1 | module.exports = fn => 2 | (req, res, next) => 3 | fn(req, res, next).catch(next); 4 | -------------------------------------------------------------------------------- /uploads/imgs/17f8c52c-8ca1-4809-b3b6-215137bdd182.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/17f8c52c-8ca1-4809-b3b6-215137bdd182.jpg -------------------------------------------------------------------------------- /uploads/imgs/187baf2d-210c-4c1d-8867-277a8c0d3056.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/187baf2d-210c-4c1d-8867-277a8c0d3056.jpg -------------------------------------------------------------------------------- /uploads/imgs/2256466d-1f79-4913-bf8b-b8183e188bcf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/2256466d-1f79-4913-bf8b-b8183e188bcf.jpg -------------------------------------------------------------------------------- /uploads/imgs/290c3726-c126-46aa-8ac6-e6ced81b0793.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/290c3726-c126-46aa-8ac6-e6ced81b0793.jpg -------------------------------------------------------------------------------- /uploads/imgs/39f424e2-6204-4dee-bed1-6d480543de5c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/39f424e2-6204-4dee-bed1-6d480543de5c.jpg -------------------------------------------------------------------------------- /uploads/imgs/3b2014e2-f9cc-467a-9b4d-aed22828bd66.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/3b2014e2-f9cc-467a-9b4d-aed22828bd66.jpg -------------------------------------------------------------------------------- /uploads/imgs/4a757683-63e0-44b3-8830-7d7793d875ac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/4a757683-63e0-44b3-8830-7d7793d875ac.jpg -------------------------------------------------------------------------------- /uploads/imgs/52cfee2d-d3bd-4b94-ba64-ff47d9d4a426.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/52cfee2d-d3bd-4b94-ba64-ff47d9d4a426.jpg -------------------------------------------------------------------------------- /uploads/imgs/5e9b04ad-8f17-4ee0-a314-083e0b9b4395.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/5e9b04ad-8f17-4ee0-a314-083e0b9b4395.jpg -------------------------------------------------------------------------------- /uploads/imgs/89ae1cf3-4ff1-4713-bd7e-d4a8c8d4e0f9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/89ae1cf3-4ff1-4713-bd7e-d4a8c8d4e0f9.jpg -------------------------------------------------------------------------------- /uploads/imgs/96ea350f-53d6-4bd3-9721-10d186ed5867.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/96ea350f-53d6-4bd3-9721-10d186ed5867.jpg -------------------------------------------------------------------------------- /uploads/imgs/a382781e-e0f6-4887-83c3-c3f3595695b8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/a382781e-e0f6-4887-83c3-c3f3595695b8.jpg -------------------------------------------------------------------------------- /uploads/imgs/d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg -------------------------------------------------------------------------------- /uploads/imgs/e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg -------------------------------------------------------------------------------- /uploads/imgs/f5918cea-a22b-470b-9053-c863b2a4a1e3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular/master/uploads/imgs/f5918cea-a22b-470b-9053-c863b2a4a1e3.jpg -------------------------------------------------------------------------------- /app/api/index.js: -------------------------------------------------------------------------------- 1 | const commentAPI = require("./comment"); 2 | const photoAPI = require("./photo"); 3 | const userAPI = require("./user"); 4 | 5 | module.exports = { commentAPI, photoAPI, userAPI }; 6 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const app = require("./config/express"); 3 | 4 | http.createServer(app).listen(3000, () => { 5 | console.log("Servidor en puerto: " + 3000); 6 | }); 7 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | const commentRoutes = require("./comment"); 2 | const photoRoutes = require("./photo"); 3 | const userRoutes = require("./user"); 4 | 5 | module.exports = { commentRoutes, photoRoutes, userRoutes }; 6 | -------------------------------------------------------------------------------- /app/infra/index.js: -------------------------------------------------------------------------------- 1 | const PhotoDao = require("./photo-dao"); 2 | const CommentDao = require("./comment-dao"); 3 | const UserDao = require("./user-dao"); 4 | const wrapAsync = require("./async-wrap"); 5 | const auth = require("./auth"); 6 | 7 | module.exports = { 8 | PhotoDao, 9 | CommentDao, 10 | UserDao, 11 | wrapAsync, 12 | auth, 13 | }; 14 | -------------------------------------------------------------------------------- /app/routes/comment.js: -------------------------------------------------------------------------------- 1 | const { commentAPI } = require("../api"); 2 | const path = require("path"); 3 | const { wrapAsync, auth } = require("../infra"); 4 | 5 | module.exports = (app) => { 6 | app 7 | .route("/photos/:photoId/comments") 8 | .get(wrapAsync(commentAPI.listAllFromPhoto)) 9 | .post(auth, wrapAsync(commentAPI.add)); 10 | }; 11 | -------------------------------------------------------------------------------- /app/routes/user.js: -------------------------------------------------------------------------------- 1 | const { userAPI } = require("../api"); 2 | const path = require("path"); 3 | const { wrapAsync } = require("../infra"); 4 | 5 | module.exports = (app) => { 6 | app.route("/user/login").post(wrapAsync(userAPI.login)); 7 | 8 | app.route("/user/signup").post(wrapAsync(userAPI.register)); 9 | 10 | app 11 | .route("/user/exists/:userName") 12 | .get(wrapAsync(userAPI.checkUserNameTaken)); 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catbook", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "cors": "^2.8.4", 15 | "cryptiles": ">=4.1.2", 16 | "express": "^4.16.3", 17 | "jimp": "^0.2.28", 18 | "jsonwebtoken": "^8.2.0", 19 | "multer": "^1.3.0", 20 | "sqlite3": "^4.1.0", 21 | "uuid": "^3.2.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/routes/photo.js: -------------------------------------------------------------------------------- 1 | const { photoAPI } = require("../api"); 2 | const path = require("path"); 3 | const { wrapAsync, auth } = require("../infra"); 4 | 5 | module.exports = (app) => { 6 | app.route("/:userName/photos").get(wrapAsync(photoAPI.list)); 7 | 8 | app 9 | .route("/photos/upload") 10 | .post( 11 | auth, 12 | app.get("upload").single("imageFile"), 13 | wrapAsync(photoAPI.addUpload) 14 | ); 15 | 16 | app 17 | .route("/photos/:photoId") 18 | .post(auth, wrapAsync(photoAPI.add)) 19 | .delete(auth, wrapAsync(photoAPI.remove)) 20 | .get(wrapAsync(photoAPI.findById)); 21 | 22 | app.route("/photos/:photoId/like").post(auth, wrapAsync(photoAPI.like)); 23 | }; 24 | -------------------------------------------------------------------------------- /app/infra/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const { promisify } = require("util"); 3 | 4 | const verify = promisify(jwt.verify); 5 | 6 | module.exports = async (req, res, next) => { 7 | const token = req.headers["x-access-token"]; 8 | if (token) { 9 | try { 10 | const decoded = await verify(token, req.app.get("secret")); 11 | console.log(`Valid token received: ${token}`); 12 | req.user = decoded; 13 | next(); 14 | } catch (err) { 15 | console.log(err); 16 | console.log(`Invalid token: ${token}`); 17 | return res.sendStatus(401); 18 | } 19 | } else { 20 | console.log("Toke is missing!"); 21 | return res.sendStatus(401); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /app/api/comment.js: -------------------------------------------------------------------------------- 1 | const { CommentDao, PhotoDao } = require('../infra'); 2 | 3 | const userCanComment = userId => photo => 4 | photo.allowComments || photo.userId === userId; 5 | 6 | const api = {}; 7 | 8 | api.add = async (req, res) => { 9 | 10 | const { photoId } = req.params; 11 | const { commentText } = req.body; 12 | 13 | const commentDao = new CommentDao(req.db); 14 | const photoDao = new PhotoDao(req.db); 15 | 16 | const photo = await photoDao.findById(photoId); 17 | const canComment = userCanComment(req.user.id)(photo); 18 | 19 | if(canComment) { 20 | const commentId = await commentDao.add(commentText, photo.id, req.user.id); 21 | const comment = await commentDao.findById(commentId); 22 | console.log(`Comment added`, comment); 23 | res.json(comment); 24 | } else { 25 | res.status(403).json({ message: 'Forbiden'}); 26 | } 27 | }; 28 | 29 | api.listAllFromPhoto = async (req, res) => { 30 | 31 | const { photoId } = req.params; 32 | console.log(`Get comments from photo ${photoId}`); 33 | const comments = await new CommentDao(req.db).listAllFromPhoto(photoId); 34 | res.json(comments); 35 | } 36 | 37 | module.exports = api; -------------------------------------------------------------------------------- /app/api/user.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const { UserDao } = require('../infra'); 3 | 4 | const api = {} 5 | 6 | api.login = async (req, res) => { 7 | const { userName, password } = req.body; 8 | console.log('####################################'); 9 | const user = await new UserDao(req.db).findByNameAndPassword(userName, password); 10 | console.log(user); 11 | if(user) { 12 | console.log(`User ${userName} authenticated`); 13 | console.log('Authentication Token added to response'); 14 | const token = jwt.sign(user, req.app.get('secret'), { 15 | expiresIn: 86400 // seconds, 24h 16 | }); 17 | res.set('x-access-token', token); 18 | return res.json(user); 19 | } else { 20 | console.log(`Authentication failed for user ${userName}`); 21 | console.log('No token generated'); 22 | res.status(401).json({ message: `Authentication failed for user ${userName}`}); 23 | } 24 | }; 25 | 26 | api.register = async (req, res) => { 27 | const user = req.body; 28 | const userId = await new UserDao(req.db).add(user); 29 | res.status(204).end(); 30 | }; 31 | 32 | api.checkUserNameTaken = async (req, res) => { 33 | const { userName } = req.params; 34 | const user = await new UserDao(req.db).findByName(userName); 35 | res.json(!!user); 36 | }; 37 | 38 | module.exports = api; -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const bodyParser = require("body-parser"); 4 | const path = require("path"); 5 | const cors = require("cors"); 6 | const db = require("./database"); 7 | const multer = require("multer"); 8 | const uuidv4 = require("uuid/v4"); 9 | const fs = require("fs"); 10 | const { commentRoutes, photoRoutes, userRoutes } = require("../app/routes"); 11 | 12 | const uploadDir = "./uploads"; 13 | if (!fs.existsSync(uploadDir)) { 14 | fs.mkdirSync(uploadDir); 15 | fs.mkdirSync(uploadDir + "/imgs"); 16 | } 17 | 18 | const storage = multer.diskStorage({ 19 | destination(req, file, cb) { 20 | cb(null, "uploads/imgs"); 21 | }, 22 | filename: function (req, file, cb) { 23 | cb(null, `${uuidv4()}${path.extname(file.originalname)}`); 24 | }, 25 | }); 26 | 27 | const upload = multer({ 28 | storage, 29 | fileFilter(req, file, cb) { 30 | console.log("Receiving image file"); 31 | cb(null, true); 32 | }, 33 | }); 34 | 35 | app.set("secret", "your secret phrase here"); 36 | app.set("upload", upload); 37 | 38 | const corsOptions = { 39 | exposedHeaders: ["x-access-token"], 40 | }; 41 | 42 | app.use(express.static("uploads")); 43 | app.use(cors(corsOptions)); 44 | app.use(bodyParser.json()); 45 | 46 | app.use((req, res, next) => { 47 | req.db = db; 48 | next(); 49 | }); 50 | 51 | app.use((req, res, next) => { 52 | const token = req.headers["x-access-token"]; 53 | console.log("####################################"); 54 | if (token) { 55 | console.log("A token is send by the application"); 56 | console.log("Token value is " + token); 57 | } else { 58 | console.log("No token is send by the the application"); 59 | } 60 | console.log("####################################"); 61 | next(); 62 | }); 63 | 64 | userRoutes(app); 65 | photoRoutes(app); 66 | commentRoutes(app); 67 | 68 | app.use("*", (req, res) => { 69 | res 70 | .status(404) 71 | .json({ message: `route ${req.originalUrl} does not exists!` }); 72 | }); 73 | 74 | app.use((err, req, res, next) => { 75 | console.error(err.stack); 76 | res.status(500).json({ message: "Internal server error" }); 77 | }); 78 | 79 | module.exports = app; 80 | -------------------------------------------------------------------------------- /app/infra/user-dao.js: -------------------------------------------------------------------------------- 1 | const userConverter = row => ({ 2 | id: row.user_id, 3 | name: row.user_name, 4 | email: row.user_email 5 | }); 6 | 7 | class UserDao { 8 | 9 | constructor(db) { 10 | this._db = db; 11 | } 12 | 13 | findByNameAndPassword(userName, password) { 14 | return new Promise((resolve, reject) => this._db.get( 15 | `SELECT * FROM user WHERE user_name = ? AND user_password = ?`, 16 | [userName, password], 17 | (err, row) => { 18 | if (err) { 19 | console.log(err); 20 | return reject('Can`t find user'); 21 | } 22 | 23 | if(row) resolve(userConverter(row)); 24 | resolve(null); 25 | } 26 | )); 27 | } 28 | 29 | findByName(userName) { 30 | 31 | return new Promise((resolve, reject) => this._db.get( 32 | `SELECT * FROM user WHERE user_name = ?`, 33 | [userName], 34 | (err, row) => { 35 | if (err) { 36 | console.log(err); 37 | return reject('Can`t find user'); 38 | } 39 | 40 | if(row) resolve(userConverter(row)); 41 | resolve(null); 42 | } 43 | )); 44 | 45 | } 46 | 47 | add(user) { 48 | return new Promise((resolve, reject) => { 49 | 50 | this._db.run(` 51 | INSERT INTO user ( 52 | user_name, 53 | user_full_name, 54 | user_email, 55 | user_password, 56 | user_join_date 57 | ) values (?,?,?,?,?) 58 | `, 59 | [ 60 | user.userName, 61 | user.fullName, 62 | user.email, 63 | user.password, 64 | new Date() 65 | ], 66 | function (err) { 67 | if (err) { 68 | console.log(err); 69 | return reject('Can`t register new user'); 70 | } 71 | console.log(`User ${user.userName} registered!`) 72 | resolve(); 73 | }); 74 | }); 75 | } 76 | 77 | } 78 | module.exports = UserDao; -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require("sqlite3").verbose(); 2 | const db = new sqlite3.Database("data.db"); 3 | 4 | const USER_SCHEMA = ` 5 | CREATE TABLE IF NOT EXISTS user ( 6 | user_id INTEGER PRIMARY KEY AUTOINCREMENT, 7 | user_name VARCHAR(30) NOT NULL UNIQUE, 8 | user_email VARCHAR(255) NOT NULL, 9 | user_password VARCAHR(255) NOT NULL, 10 | user_full_name VARCAHR(40) NOT NULL, 11 | user_join_date TIMESTAMP DEFAULT current_timestamp 12 | ) 13 | `; 14 | 15 | const INSERT_DEFAULT_USER_1 = ` 16 | INSERT INTO user ( 17 | user_name, 18 | user_email, 19 | user_password, 20 | user_full_name 21 | ) SELECT 'usuario', 'usuario@email.com', '12345678', 'Usuario' WHERE NOT EXISTS (SELECT * FROM user WHERE user_name = 'usuario') 22 | `; 23 | 24 | const PHOTO_SCHEMA = ` 25 | CREATE TABLE IF NOT EXISTS photo ( 26 | photo_id INTEGER PRIMARY KEY AUTOINCREMENT, 27 | photo_post_date TIMESTAMP NOT NULL, 28 | photo_url TEXT NOT NULL, 29 | photo_description TEXT DEFAULT ('') NOT NULL, 30 | photo_allow_comments INTEGER NOT NULL DEFAULT (1), 31 | photo_likes BIGINT NOT NULL DEFAULT (0), 32 | user_id INTEGER, 33 | FOREIGN KEY(user_id) REFERENCES user(user_id) ON DELETE CASCADE 34 | ) 35 | `; 36 | 37 | const COMMENT_SCHEMA = ` 38 | CREATE TABLE IF NOT EXISTS comment ( 39 | comment_id INTEGER PRIMARY KEY AUTOINCREMENT, 40 | comment_date TIMESTAMP NOT NULL, 41 | comment_text TEXT DEFAULT (''), 42 | photo_id INTEGER, 43 | user_id INTEGER, 44 | FOREIGN KEY (photo_id) REFERENCES photo (photo_id) ON DELETE CASCADE, 45 | FOREIGN KEY(user_id) REFERENCES user(user_id) ON DELETE CASCADE 46 | ); 47 | `; 48 | 49 | const LIKE_SCHEMA = ` 50 | CREATE TABLE IF NOT EXISTS like ( 51 | like_id INTEGER PRIMARY KEY AUTOINCREMENT, 52 | photo_id INTEGER, 53 | user_id INTEGER, 54 | like_date TIMESTAMP DEFAULT current_timestamp, 55 | UNIQUE(user_id, photo_id ), 56 | FOREIGN KEY (photo_id) REFERENCES photo (photo_id) ON DELETE CASCADE, 57 | FOREIGN KEY(user_id) REFERENCES user(user_id) ON DELETE CASCADE 58 | ) 59 | `; 60 | 61 | db.serialize(() => { 62 | db.run("PRAGMA foreign_keys=ON"); 63 | db.run(USER_SCHEMA); 64 | db.run(INSERT_DEFAULT_USER_1); 65 | db.run(PHOTO_SCHEMA); 66 | db.run(COMMENT_SCHEMA); 67 | db.run(LIKE_SCHEMA); 68 | 69 | db.each("SELECT * FROM user", (err, user) => { 70 | console.log("Users"); 71 | console.log(user); 72 | }); 73 | }); 74 | 75 | process.on("SIGINT", () => 76 | db.close(() => { 77 | console.log("Database closed"); 78 | process.exit(0); 79 | }) 80 | ); 81 | 82 | module.exports = db; 83 | -------------------------------------------------------------------------------- /app/infra/comment-dao.js: -------------------------------------------------------------------------------- 1 | const commentConverter = row => ({ 2 | date: row.comment_date, 3 | text: row.comment_text, 4 | userName: row.user_name 5 | }) 6 | 7 | class CommentDao { 8 | 9 | constructor(db) { 10 | this._db = db; 11 | } 12 | 13 | add(text, photoId, userId) { 14 | return new Promise((resolve, reject) => { 15 | this._db.run(` 16 | INSERT INTO comment ( 17 | comment_date, 18 | comment_text, 19 | photo_id, 20 | user_id 21 | ) values (?,?,?, ?) 22 | `, 23 | [ 24 | new Date(), 25 | text, 26 | photoId, 27 | userId, 28 | ], 29 | function (err) { 30 | if (err) { 31 | console.log(err); 32 | return reject('Can`t add comment'); 33 | } 34 | resolve(this.lastID); 35 | }); 36 | }); 37 | } 38 | 39 | listAllFromPhoto(photoId) { 40 | 41 | return new Promise((resolve, reject) => { 42 | this._db.all( 43 | ` 44 | SELECT 45 | c.comment_date, c.comment_text, u.user_name 46 | FROM comment as c 47 | JOIN user as u ON u.user_id = c.user_id 48 | WHERE c.photo_id = ? 49 | ORDER BY c.comment_date DESC 50 | `, 51 | [photoId], 52 | (err, rows) => { 53 | 54 | if (err) { 55 | console.log(err); 56 | return reject('Can`t load comments'); 57 | } 58 | const comments = rows.map(commentConverter); 59 | return resolve(comments); 60 | } 61 | ); 62 | 63 | }); 64 | } 65 | 66 | findById(commentId) { 67 | 68 | return new Promise((resolve, reject) => { 69 | this._db.get( 70 | ` 71 | SELECT 72 | c.comment_date, c.comment_text, u.user_name 73 | FROM comment as c 74 | JOIN user as u ON u.user_id = c.user_id 75 | WHERE c.comment_id = ? 76 | `, 77 | [commentId], 78 | (err, row) => { 79 | console.log(row); 80 | if (err) { 81 | console.log(err); 82 | return reject('Can`t load comment'); 83 | } 84 | return resolve(commentConverter(row)); 85 | } 86 | ); 87 | 88 | }); 89 | } 90 | } 91 | 92 | module.exports = CommentDao; -------------------------------------------------------------------------------- /app/api/photo.js: -------------------------------------------------------------------------------- 1 | const { PhotoDao, UserDao } = require('../infra') 2 | , jimp = require('jimp') 3 | , path = require('path') 4 | , fs = require('fs') 5 | , unlink = require('util').promisify(fs.unlink); 6 | 7 | const api = {} 8 | 9 | const userCanDelete = user => photo => photo.userId == user.id; 10 | 11 | const defaultExtension = '.jpg'; 12 | 13 | api.list = async (req, res) => { 14 | console.log('####################################'); 15 | const { userName } = req.params; 16 | const { page } = req.query; 17 | const user = await new UserDao(req.db).findByName(userName); 18 | if(user) { 19 | console.log(`Listing photos`); 20 | const photos = await new PhotoDao(req.db) 21 | .listAllFromUser(userName, page); 22 | res.json(photos); 23 | } else { 24 | res.status(404).json({ message: 'User not found'}); 25 | } 26 | 27 | } 28 | 29 | api.add = async (req, res) => { 30 | console.log('####################################'); 31 | console.log('Received JSON data', req.body); 32 | const photo = req.body; 33 | photo.file = ''; 34 | const id = await new PhotoDao(req.db).add(photo, req.user.id); 35 | res.json(id); 36 | }; 37 | 38 | api.addUpload = async (req, res) => { 39 | 40 | console.log('upload complete'); 41 | console.log('Photo data', req.body); 42 | console.log('File info', req.file); 43 | 44 | const image = await jimp.read(req.file.path); 45 | 46 | await image 47 | .exifRotate() 48 | .cover(460, 460) 49 | .autocrop() 50 | .write(req.file.path); 51 | 52 | const photo = req.body; 53 | photo.url = path.basename(req.file.path); 54 | await new PhotoDao(req.db).add(photo, req.user.id); 55 | res.status(200).end(); 56 | }; 57 | 58 | api.findById = async (req, res) => { 59 | const { photoId } = req.params; 60 | console.log('####################################'); 61 | console.log(`Finding photo for ID ${photoId}`) 62 | const photo = await new PhotoDao(req.db).findById(photoId); 63 | if(photo) { 64 | res.json(photo); 65 | } else { 66 | res.status(404).json({ message: 'Photo does not exist'}) 67 | } 68 | }; 69 | 70 | api.remove = async (req, res) => { 71 | const user = req.user; 72 | const { photoId } = req.params; 73 | const dao = new PhotoDao(req.db); 74 | const photo = await dao.findById(photoId); 75 | if(!photo) { 76 | const message = 'Photo does not exist'; 77 | console.log(message); 78 | return res.status(404).json({ message }); 79 | } 80 | 81 | if(userCanDelete(user)(photo)) { 82 | await dao.remove(photoId) 83 | console.log(`Photo ${photoId} deleted!`); 84 | res.status(200).end(); 85 | } else { 86 | console.log(` 87 | Forbiden operation. User ${user.id} 88 | can delete photo from user ${photo.userId} 89 | `); 90 | res.status(403).json({ message: 'Forbidden'}); 91 | } 92 | }; 93 | 94 | api.like = async (req, res) => { 95 | const { photoId } = req.params; 96 | const dao = new PhotoDao(req.db); 97 | const liked = await dao.likeById(photoId, req.user.id); 98 | if(liked) { 99 | console.log(`User ${req.user.name} liked photo ${photoId}`); 100 | return res.status(201).end(); 101 | } 102 | return res.status(304).end(); 103 | }; 104 | 105 | module.exports = api; -------------------------------------------------------------------------------- /app/infra/photo-dao.js: -------------------------------------------------------------------------------- 1 | const photoConverter = row => ({ 2 | id: row.photo_id, 3 | postDate: new Date(row.photo_post_date), 4 | url: row.photo_url, 5 | description: row.photo_description, 6 | allowComments: row.photo_allow_comments == 'true' ? true : false, 7 | likes: row.likes, 8 | comments: row.comments, 9 | userId: row.user_id, 10 | }); 11 | 12 | const commentConverter = row => ({ 13 | date: row.comment_date, 14 | text: row.comment_text, 15 | userName: row.user_name 16 | }) 17 | 18 | const maxRows = 12; 19 | 20 | class PhotoDao { 21 | 22 | constructor(db) { 23 | this._db = db; 24 | } 25 | 26 | listAllFromUser(userName, page) { 27 | 28 | const from = (page - 1) * maxRows; 29 | 30 | let limitQuery = ''; 31 | 32 | if (page) limitQuery = `LIMIT ${from}, ${maxRows}`; 33 | 34 | return new Promise((resolve, reject) => { 35 | this._db.all(` 36 | SELECT p.*, 37 | (SELECT COUNT(c.comment_id) 38 | FROM comment as c 39 | WHERE c.photo_id = p.photo_id 40 | ) as comments, 41 | 42 | (SELECT COUNT(l.like_id) 43 | FROM like as l 44 | WHERE l.photo_id = p.photo_id 45 | ) as likes 46 | FROM photo AS p 47 | JOIN 48 | user AS u ON p.user_id = u.user_id 49 | WHERE u.user_name = ? 50 | ORDER BY p.photo_post_date DESC 51 | ${limitQuery} ; 52 | `, 53 | [userName], 54 | (err, rows) => { 55 | const photos = rows.map(photoConverter) 56 | if (err) { 57 | console.log(err); 58 | return reject('Can`t list photos'); 59 | } 60 | console.log('photos retornadas'); 61 | resolve(photos); 62 | }); 63 | }); 64 | } 65 | 66 | add(photo, user_id) { 67 | return new Promise((resolve, reject) => { 68 | this._db.run(` 69 | INSERT INTO photo ( 70 | photo_post_date, 71 | photo_url, 72 | photo_description, 73 | photo_allow_comments, 74 | user_id 75 | ) values (?,?,?,?,?) 76 | `, 77 | [ 78 | new Date(), 79 | photo.url, 80 | photo.description, 81 | photo.allowComments, 82 | user_id 83 | ], 84 | function (err) { 85 | if (err) { 86 | console.log(err); 87 | return reject('Can`t add photo'); 88 | } 89 | resolve(this.lastID); 90 | }); 91 | }); 92 | } 93 | 94 | findById(id) { 95 | 96 | return new Promise((resolve, reject) => this._db.get(` 97 | SELECT p.*, 98 | (SELECT COUNT(c.comment_id) 99 | FROM comment as c 100 | WHERE c.photo_id = p.photo_id 101 | ) as comments, 102 | (SELECT COUNT(l.like_id) 103 | FROM like as l 104 | WHERE l.photo_id = p.photo_id 105 | ) as likes 106 | FROM photo AS p 107 | WHERE p.photo_id = ? 108 | ORDER BY p.photo_post_date DESC; 109 | `, 110 | [id], 111 | (err, row) => { 112 | if (err) { 113 | console.log(err); 114 | return reject('Can`t find photo'); 115 | } 116 | if (row) { 117 | resolve(photoConverter(row)); 118 | } else { 119 | resolve(null); 120 | } 121 | } 122 | )); 123 | } 124 | 125 | remove(id) { 126 | return new Promise((resolve, reject) => this._db.run( 127 | `DELETE FROM photo where photo_id = ?`, 128 | [id], 129 | err => { 130 | if (err) { 131 | console.log(err); 132 | return reject('Can`t remove photo'); 133 | } 134 | resolve(); 135 | } 136 | )); 137 | } 138 | 139 | addComment(text, photoId, userId) { 140 | return new Promise((resolve, reject) => { 141 | this._db.run(` 142 | INSERT INTO comment ( 143 | comment_date, 144 | comment_text, 145 | photo_id, 146 | user_id 147 | ) values (?,?,?, ?) 148 | `, 149 | [ 150 | new Date(), 151 | text, 152 | photoId, 153 | userId, 154 | ], 155 | function (err) { 156 | if (err) { 157 | console.log(err); 158 | return reject('Can`t add comment'); 159 | } 160 | resolve(this.lastID); 161 | }); 162 | }); 163 | } 164 | 165 | getCommentsFromPhoto(photoId) { 166 | 167 | return new Promise((resolve, reject) => { 168 | this._db.all( 169 | ` 170 | SELECT 171 | c.comment_date, c.comment_text, u.user_name 172 | FROM comment as c 173 | JOIN user as u ON u.user_id = c.user_id 174 | WHERE c.photo_id = ? 175 | ORDER BY c.comment_date DESC 176 | `, 177 | [photoId], 178 | (err, rows) => { 179 | 180 | if (err) { 181 | console.log(err); 182 | return reject('Can`t load comments'); 183 | } 184 | const comments = rows.map(commentConverter); 185 | return resolve(comments); 186 | } 187 | ); 188 | 189 | }); 190 | } 191 | 192 | findCommentById(commentId) { 193 | 194 | return new Promise((resolve, reject) => { 195 | this._db.get( 196 | ` 197 | SELECT 198 | c.comment_date, c.comment_text, u.user_name 199 | FROM comment as c 200 | JOIN user as u ON u.user_id = c.user_id 201 | WHERE c.comment_id = ? 202 | `, 203 | [commentId], 204 | (err, row) => { 205 | console.log(row); 206 | if (err) { 207 | console.log(err); 208 | return reject('Can`t load comment'); 209 | } 210 | return resolve(commentConverter(row)); 211 | } 212 | ); 213 | 214 | }); 215 | } 216 | 217 | likeById(photoId, userId) { 218 | 219 | return new Promise((resolve, reject) => this._db.run( 220 | ` 221 | INSERT OR IGNORE INTO like 222 | (photo_id, user_id) 223 | VALUES 224 | (?, ?) 225 | `, 226 | [photoId, userId], 227 | function(err) { 228 | if (err) { 229 | console.log(err); 230 | return reject('Cant like photo'); 231 | } 232 | resolve(!!this.changes); 233 | } 234 | )); 235 | 236 | } 237 | } 238 | 239 | module.exports = PhotoDao; --------------------------------------------------------------------------------