├── .gitignore ├── data.db ├── 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 │ ├── 041635d8-6ed0-4a80-9c14-d604c83bb76d.jpg │ ├── 0dc412d8-7df3-4636-a63d-53265f4f94cd.jpg │ ├── 17534a1c-a12f-463a-824a-5d4b80aa65aa.jpg │ ├── 17f8c52c-8ca1-4809-b3b6-215137bdd182.jpg │ ├── 187baf2d-210c-4c1d-8867-277a8c0d3056.jpg │ ├── 2256466d-1f79-4913-bf8b-b8183e188bcf.jpg │ ├── 290c3726-c126-46aa-8ac6-e6ced81b0793.jpg │ ├── 2c8219c6-ac00-4ac6-bd64-139d24059f83.jpg │ ├── 39f424e2-6204-4dee-bed1-6d480543de5c.jpg │ ├── 3b2014e2-f9cc-467a-9b4d-aed22828bd66.jpg │ ├── 3d2533ec-d37f-4236-968b-a8a8c25c0a7f.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 │ ├── 8e7e950a-8c11-4a4d-b011-96fb5ef70320.jpg │ ├── 9294382f-6738-40f1-9658-cdd7f9d5872b.jpg │ ├── 96ea350f-53d6-4bd3-9721-10d186ed5867.jpg │ ├── 99e67540-25b8-47d5-b318-04162d0b7b72.jpg │ ├── 9b03b79d-38af-4dfc-8f26-0ce8ba8b3da6.jpg │ ├── a382781e-e0f6-4887-83c3-c3f3595695b8.jpg │ ├── a4326549-4280-460a-8dc4-69a92fcbf52e.jpg │ ├── acd785fb-ac2c-4f61-9fa4-c422bce60555.jpg │ ├── d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg │ ├── d68d1b4c-ae42-4a46-a588-dd65d9a82503.jpg │ ├── d79e1366-5504-4019-9f6c-3f9606435897.jpg │ ├── e6592644-5b4b-419d-ba46-0c231a7d6ac5.jpg │ ├── e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg │ ├── f3f996ab-299f-4f33-b920-02214add5c69.jpg │ └── f5918cea-a22b-470b-9053-c863b2a4a1e3.jpg ├── server.js ├── package.json └── config ├── express.js └── database.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/data.db -------------------------------------------------------------------------------- /app/infra/async-wrap.js: -------------------------------------------------------------------------------- 1 | module.exports = fn => 2 | (req, res, next) => 3 | fn(req, res, next).catch(next); 4 | -------------------------------------------------------------------------------- /uploads/imgs/041635d8-6ed0-4a80-9c14-d604c83bb76d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/041635d8-6ed0-4a80-9c14-d604c83bb76d.jpg -------------------------------------------------------------------------------- /uploads/imgs/0dc412d8-7df3-4636-a63d-53265f4f94cd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/0dc412d8-7df3-4636-a63d-53265f4f94cd.jpg -------------------------------------------------------------------------------- /uploads/imgs/17534a1c-a12f-463a-824a-5d4b80aa65aa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/17534a1c-a12f-463a-824a-5d4b80aa65aa.jpg -------------------------------------------------------------------------------- /uploads/imgs/17f8c52c-8ca1-4809-b3b6-215137bdd182.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/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-v2/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-v2/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-v2/master/uploads/imgs/290c3726-c126-46aa-8ac6-e6ced81b0793.jpg -------------------------------------------------------------------------------- /uploads/imgs/2c8219c6-ac00-4ac6-bd64-139d24059f83.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/2c8219c6-ac00-4ac6-bd64-139d24059f83.jpg -------------------------------------------------------------------------------- /uploads/imgs/39f424e2-6204-4dee-bed1-6d480543de5c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/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-v2/master/uploads/imgs/3b2014e2-f9cc-467a-9b4d-aed22828bd66.jpg -------------------------------------------------------------------------------- /uploads/imgs/3d2533ec-d37f-4236-968b-a8a8c25c0a7f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/3d2533ec-d37f-4236-968b-a8a8c25c0a7f.jpg -------------------------------------------------------------------------------- /uploads/imgs/4a757683-63e0-44b3-8830-7d7793d875ac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/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-v2/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-v2/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-v2/master/uploads/imgs/89ae1cf3-4ff1-4713-bd7e-d4a8c8d4e0f9.jpg -------------------------------------------------------------------------------- /uploads/imgs/8e7e950a-8c11-4a4d-b011-96fb5ef70320.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/8e7e950a-8c11-4a4d-b011-96fb5ef70320.jpg -------------------------------------------------------------------------------- /uploads/imgs/9294382f-6738-40f1-9658-cdd7f9d5872b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/9294382f-6738-40f1-9658-cdd7f9d5872b.jpg -------------------------------------------------------------------------------- /uploads/imgs/96ea350f-53d6-4bd3-9721-10d186ed5867.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/96ea350f-53d6-4bd3-9721-10d186ed5867.jpg -------------------------------------------------------------------------------- /uploads/imgs/99e67540-25b8-47d5-b318-04162d0b7b72.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/99e67540-25b8-47d5-b318-04162d0b7b72.jpg -------------------------------------------------------------------------------- /uploads/imgs/9b03b79d-38af-4dfc-8f26-0ce8ba8b3da6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/9b03b79d-38af-4dfc-8f26-0ce8ba8b3da6.jpg -------------------------------------------------------------------------------- /uploads/imgs/a382781e-e0f6-4887-83c3-c3f3595695b8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/a382781e-e0f6-4887-83c3-c3f3595695b8.jpg -------------------------------------------------------------------------------- /uploads/imgs/a4326549-4280-460a-8dc4-69a92fcbf52e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/a4326549-4280-460a-8dc4-69a92fcbf52e.jpg -------------------------------------------------------------------------------- /uploads/imgs/acd785fb-ac2c-4f61-9fa4-c422bce60555.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/acd785fb-ac2c-4f61-9fa4-c422bce60555.jpg -------------------------------------------------------------------------------- /uploads/imgs/d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/d1ce97e6-47fa-4759-91fb-f9b009c119e1.jpg -------------------------------------------------------------------------------- /uploads/imgs/d68d1b4c-ae42-4a46-a588-dd65d9a82503.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/d68d1b4c-ae42-4a46-a588-dd65d9a82503.jpg -------------------------------------------------------------------------------- /uploads/imgs/d79e1366-5504-4019-9f6c-3f9606435897.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/d79e1366-5504-4019-9f6c-3f9606435897.jpg -------------------------------------------------------------------------------- /uploads/imgs/e6592644-5b4b-419d-ba46-0c231a7d6ac5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/e6592644-5b4b-419d-ba46-0c231a7d6ac5.jpg -------------------------------------------------------------------------------- /uploads/imgs/e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/e754e7a6-9191-418c-9beb-51e9ad530fd8.jpg -------------------------------------------------------------------------------- /uploads/imgs/f3f996ab-299f-4f33-b920-02214add5c69.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/master/uploads/imgs/f3f996ab-299f-4f33-b920-02214add5c69.jpg -------------------------------------------------------------------------------- /uploads/imgs/f5918cea-a22b-470b-9053-c863b2a4a1e3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-es-cursos/1867-backendAngular-v2/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": "^5.0.2", 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 | .cover(460, 460) 48 | .autocrop() 49 | .write(req.file.path); 50 | 51 | const photo = req.body; 52 | photo.url = path.basename(req.file.path); 53 | await new PhotoDao(req.db).add(photo, req.user.id); 54 | res.status(200).end(); 55 | }; 56 | 57 | api.findById = async (req, res) => { 58 | const { photoId } = req.params; 59 | console.log('####################################'); 60 | console.log(`Finding photo for ID ${photoId}`) 61 | const photo = await new PhotoDao(req.db).findById(photoId); 62 | if (photo) { 63 | res.json(photo); 64 | } else { 65 | res.status(404).json({ message: 'Photo does not exist' }) 66 | } 67 | }; 68 | 69 | api.remove = async (req, res) => { 70 | const user = req.user; 71 | const { photoId } = req.params; 72 | const dao = new PhotoDao(req.db); 73 | const photo = await dao.findById(photoId); 74 | if (!photo) { 75 | const message = 'Photo does not exist'; 76 | console.log(message); 77 | return res.status(404).json({ message }); 78 | } 79 | 80 | if (userCanDelete(user)(photo)) { 81 | await dao.remove(photoId) 82 | console.log(`Photo ${photoId} deleted!`); 83 | res.status(200).end(); 84 | } else { 85 | console.log(` 86 | Forbiden operation. User ${user.id} 87 | can delete photo from user ${photo.userId} 88 | `); 89 | res.status(403).json({ message: 'Forbidden' }); 90 | } 91 | }; 92 | 93 | api.like = async (req, res) => { 94 | const { photoId } = req.params; 95 | const dao = new PhotoDao(req.db); 96 | const liked = await dao.likeById(photoId, req.user.id); 97 | if (liked) { 98 | console.log(`User ${req.user.name} liked photo ${photoId}`); 99 | return res.status(201).end(); 100 | } 101 | return res.status(304).end(); 102 | }; 103 | 104 | 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 | console.log('/////', userName) 29 | 30 | const from = (page - 1) * maxRows; 31 | 32 | let limitQuery = ''; 33 | 34 | if (page) limitQuery = `LIMIT ${from}, ${maxRows}`; 35 | 36 | return new Promise((resolve, reject) => { 37 | this._db.all(` 38 | SELECT p.*, 39 | (SELECT COUNT(c.comment_id) 40 | FROM comment as c 41 | WHERE c.photo_id = p.photo_id 42 | ) as comments, 43 | 44 | (SELECT COUNT(l.like_id) 45 | FROM like as l 46 | WHERE l.photo_id = p.photo_id 47 | ) as likes 48 | FROM photo AS p 49 | JOIN 50 | user AS u ON p.user_id = u.user_id 51 | WHERE u.user_name = ? 52 | ORDER BY p.photo_post_date DESC 53 | ${limitQuery} ; 54 | `, 55 | [userName], 56 | (err, rows) => { 57 | const photos = rows.map(photoConverter) 58 | if (err) { 59 | console.log(err); 60 | return reject('Can`t list photos'); 61 | } 62 | console.log(photos) 63 | console.log('photos retornadas'); 64 | resolve(photos); 65 | }); 66 | }); 67 | } 68 | 69 | add(photo, user_id) { 70 | return new Promise((resolve, reject) => { 71 | this._db.run(` 72 | INSERT INTO photo ( 73 | photo_post_date, 74 | photo_url, 75 | photo_description, 76 | photo_allow_comments, 77 | user_id 78 | ) values (?,?,?,?,?) 79 | `, 80 | [ 81 | new Date(), 82 | photo.url, 83 | photo.description, 84 | photo.allowComments, 85 | user_id 86 | ], 87 | function (err) { 88 | if (err) { 89 | console.log(err); 90 | return reject('Can`t add photo'); 91 | } 92 | resolve(this.lastID); 93 | }); 94 | }); 95 | } 96 | 97 | findById(id) { 98 | 99 | return new Promise((resolve, reject) => this._db.get(` 100 | SELECT p.*, 101 | (SELECT COUNT(c.comment_id) 102 | FROM comment as c 103 | WHERE c.photo_id = p.photo_id 104 | ) as comments, 105 | (SELECT COUNT(l.like_id) 106 | FROM like as l 107 | WHERE l.photo_id = p.photo_id 108 | ) as likes 109 | FROM photo AS p 110 | WHERE p.photo_id = ? 111 | ORDER BY p.photo_post_date DESC; 112 | `, 113 | [id], 114 | (err, row) => { 115 | if (err) { 116 | console.log(err); 117 | return reject('Can`t find photo'); 118 | } 119 | if (row) { 120 | resolve(photoConverter(row)); 121 | } else { 122 | resolve(null); 123 | } 124 | } 125 | )); 126 | } 127 | 128 | remove(id) { 129 | return new Promise((resolve, reject) => this._db.run( 130 | `DELETE FROM photo where photo_id = ?`, 131 | [id], 132 | err => { 133 | if (err) { 134 | console.log(err); 135 | return reject('Can`t remove photo'); 136 | } 137 | resolve(); 138 | } 139 | )); 140 | } 141 | 142 | addComment(text, photoId, userId) { 143 | return new Promise((resolve, reject) => { 144 | this._db.run(` 145 | INSERT INTO comment ( 146 | comment_date, 147 | comment_text, 148 | photo_id, 149 | user_id 150 | ) values (?,?,?, ?) 151 | `, 152 | [ 153 | new Date(), 154 | text, 155 | photoId, 156 | userId, 157 | ], 158 | function (err) { 159 | if (err) { 160 | console.log(err); 161 | return reject('Can`t add comment'); 162 | } 163 | resolve(this.lastID); 164 | }); 165 | }); 166 | } 167 | 168 | getCommentsFromPhoto(photoId) { 169 | 170 | return new Promise((resolve, reject) => { 171 | this._db.all( 172 | ` 173 | SELECT 174 | c.comment_date, c.comment_text, u.user_name 175 | FROM comment as c 176 | JOIN user as u ON u.user_id = c.user_id 177 | WHERE c.photo_id = ? 178 | ORDER BY c.comment_date DESC 179 | `, 180 | [photoId], 181 | (err, rows) => { 182 | 183 | if (err) { 184 | console.log(err); 185 | return reject('Can`t load comments'); 186 | } 187 | const comments = rows.map(commentConverter); 188 | return resolve(comments); 189 | } 190 | ); 191 | 192 | }); 193 | } 194 | 195 | findCommentById(commentId) { 196 | 197 | return new Promise((resolve, reject) => { 198 | this._db.get( 199 | ` 200 | SELECT 201 | c.comment_date, c.comment_text, u.user_name 202 | FROM comment as c 203 | JOIN user as u ON u.user_id = c.user_id 204 | WHERE c.comment_id = ? 205 | `, 206 | [commentId], 207 | (err, row) => { 208 | console.log(row); 209 | if (err) { 210 | console.log(err); 211 | return reject('Can`t load comment'); 212 | } 213 | return resolve(commentConverter(row)); 214 | } 215 | ); 216 | 217 | }); 218 | } 219 | 220 | likeById(photoId, userId) { 221 | 222 | return new Promise((resolve, reject) => this._db.run( 223 | ` 224 | INSERT OR IGNORE INTO like 225 | (photo_id, user_id) 226 | VALUES 227 | (?, ?) 228 | `, 229 | [photoId, userId], 230 | function (err) { 231 | if (err) { 232 | console.log(err); 233 | return reject('Cant like photo'); 234 | } 235 | resolve(!!this.changes); 236 | } 237 | )); 238 | 239 | } 240 | } 241 | 242 | module.exports = PhotoDao; --------------------------------------------------------------------------------