├── .gitignore ├── README.md ├── database ├── connect-database ├── create-database ├── destroy-database ├── scripts │ └── 001-create-db.sql └── src │ ├── database │ └── functions ├── db.js ├── package-lock.json ├── package.json └── src ├── app.js ├── controllers ├── authController.js ├── commentController.js ├── hashtagsController.js ├── postController.js ├── repostController.js ├── searchBarController.js └── usersController.js ├── middlewares ├── validateHashtagMiddleware.js ├── validateSchemaMiddleware.js └── validateTokenMiddleware.js ├── repositories ├── authRepository.js ├── commentRepository.js ├── hashtagRepository.js ├── postRepository.js ├── repostRepository.js ├── searchBarRepository.js └── userRepository.js ├── routes ├── authRouter.js ├── commentRouter.js ├── hashtagsRouter.js ├── index.js ├── postRouter.js ├── repostRouter.js ├── searchBarRouter.js └── userRouter.js ├── schemas ├── commentSchema.js ├── followSchema.js ├── postSchema.js ├── signinSchema.js └── signupSchema.js └── utils └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linkr ⛓️ 2 | ## Share and tag links! 3 | 4 | ### :computer: Tech used 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Not needed but recommended: VSCode
26 | 27 | #### This API was designed to work with [Linktr App](https://github.com/blarth/linktr-react) 28 | 29 | Use a terminal interface such as bash or zsh, and enter the following: 30 | ```bash 31 | #download 32 | gh repo clone blarth/linktr-api 33 | 34 | #access the folder you downloaded it to 35 | cd linktr-api 36 | 37 | #install dependencies 38 | npm i 39 | ``` 40 | The app will run locally and you must configure a ```.env``` file with a port of your choosing. The default one is 3000. 41 | 42 | ## :gear:Running 43 | ```bash 44 | #you can run the server with 45 | npm run dev 46 | ``` 47 | In another terminal instance, run the db 48 | ```bash 49 | #you can start the db with 50 | psql 51 | ``` 52 | 53 | ## :scroll:Documentation 54 | 55 | ### All requests are HTTP based 56 | 57 | #### ```POST /signup, /signin, DELETE /signout``` 58 | 59 | Authenticating related routes. Send requests of the respective types with the following: 60 | 61 | To signup: 62 | 63 | { 64 | 65 | email: any email, 66 | password: no restrictions, it will be stored encrypted, 67 | username: name with which the user will be identified, 68 | image: link to an image file, it will be shown as the user's avatar, supported extensions are png, jpg, jpeg, jfif, gif 69 | 70 | } 71 | 72 | To signin: 73 | 74 | { 75 | 76 | email: email used to signup, 77 | password: password chosen upon signup 78 | 79 | } 80 | 81 | The server will respond with a token, to be used on every other route where authetication is required. Use in the headers of your requests as: 82 | 83 | { 84 | 85 | Authorization: BearerMade with care by
178 |Contact us anytime!
203 | -------------------------------------------------------------------------------- /database/connect-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checking postgres status..." && 8 | checkPostgres && 9 | 10 | enterPostgresCli $DATABASE; 11 | -------------------------------------------------------------------------------- /database/create-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checking postgres status..." && 8 | checkPostgres && 9 | 10 | echo "Creating database..." && 11 | createDatabase $DATABASE && 12 | 13 | echo "Running scripts..." && 14 | runScripts $DATABASE; 15 | -------------------------------------------------------------------------------- /database/destroy-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checking postgres status..." && 8 | checkPostgres && 9 | 10 | echo "Destroying database..." && 11 | destroyDatabase $DATABASE; 12 | -------------------------------------------------------------------------------- /database/scripts/001-create-db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "users" ( 2 | "id" SERIAL PRIMARY KEY, 3 | "name" TEXT NOT NULL, 4 | "email" TEXT NOT NULL UNIQUE, 5 | "password" TEXT NOT NULL, 6 | "image" TEXT NOT NULL 7 | ); 8 | 9 | CREATE TABLE "posts" ( 10 | "id" SERIAL PRIMARY KEY, 11 | "link" TEXT NOT NULL, 12 | "postText" TEXT, 13 | "userId" INTEGER NOT NULL REFERENCES "users"("id") 14 | ); 15 | 16 | CREATE TABLE "hashtags" ( 17 | "id" SERIAL PRIMARY KEY, 18 | "name" TEXT NOT NULL UNIQUE 19 | ); 20 | 21 | CREATE TABLE "hashtagsPosts" ( 22 | "id" SERIAL PRIMARY KEY, 23 | "hashtagId" INTEGER NOT NULL REFERENCES "hashtags"("id"), 24 | "postId" INTEGER NOT NULL REFERENCES "posts"("id") 25 | ); 26 | 27 | CREATE TABLE "likesPosts" ( 28 | "id" serial NOT NULL, 29 | "userId" INTEGER NOT NULL REFERENCES "users"("id"), 30 | "postId" INTEGER NOT NULL REFERENCES "posts"("id"), 31 | "like" BOOLEAN NOT NULL 32 | ); 33 | 34 | CREATE TABLE "sessions" ( 35 | "id" SERIAL PRIMARY KEY, 36 | "token" TEXT NOT NULL UNIQUE, 37 | "userId" INTEGER NOT NULL REFERENCES "users"("id") 38 | ); 39 | 40 | CREATE TABLE "metaData" ( 41 | "id" SERIAL PRIMARY KEY, 42 | "postId" INTEGER NOT NULL REFERENCES "posts"("id"), 43 | "url" TEXT NOT NULL, 44 | "title" TEXT, 45 | "description" TEXT NOT NULL, 46 | "image" TEXT NOT NULL 47 | ); 48 | 49 | CREATE TABLE "followers"( 50 | "id" SERIAL PRIMARY KEY, 51 | "userId" INTEGER NOT NULL REFERENCES "users"("id"), 52 | "followedByUserId" INTEGER NOT NULL REFERENCES "users"("id") 53 | ); 54 | 55 | CREATE TABLE "comments"( 56 | "id" SERIAL PRIMARY KEY, 57 | "postId" INTEGER NOT NULL REFERENCES "posts"("id"), 58 | "comment" TEXT NOT NULL, 59 | "userId" INTEGER NOT NULL REFERENCES "users"("id") 60 | ); 61 | 62 | CREATE TABLE "shares"( 63 | "id" SERIAL PRIMARY KEY, 64 | "postId" INTEGER NOT NULL REFERENCES "posts"("id"), 65 | "userId" INTEGER NOT NULL REFERENCES "users"("id"), 66 | "userName" TEXT NOT NULL 67 | ); -------------------------------------------------------------------------------- /database/src/database: -------------------------------------------------------------------------------- 1 | linktr 2 | -------------------------------------------------------------------------------- /database/src/functions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | checkPostgres () { 4 | if [[ $(id -u postgres 2> /dev/null) = "" ]]; then 5 | echo "It looks like postgres isnt installed!"; 6 | echo "Run: sudo apt update && sudo apt install postgresql postgresql-contrib"; 7 | exit; 8 | fi 9 | 10 | if [[ $(pgrep -u postgres -fa -- -D) = "" ]]; then 11 | echo "PostgreSQL isnt running, trying to restart..."; 12 | sudo service postgresql start; 13 | fi 14 | } 15 | 16 | createDatabase () { 17 | sudo su -c "psql -c \"CREATE DATABASE $1\";" postgres; 18 | } 19 | 20 | destroyDatabase () { 21 | sudo su -c "psql -c \"DROP DATABASE $1\";" postgres; 22 | } 23 | 24 | runScripts () { 25 | for f in scripts/*; do 26 | echo "Running script $f..."; 27 | sudo su -c "psql -d $1 -f $f" postgres; 28 | done 29 | } 30 | 31 | enterPostgresCli () { 32 | sudo su -c "psql -d $1" postgres; 33 | } 34 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | import "dotenv/config"; 3 | 4 | const { Pool } = pg; 5 | 6 | const connection = new Pool({ 7 | connectionString: process.env.DATABASE_URL, 8 | ssl: process.env.NODE_ENV === "production" ?{ 9 | rejectUnauthorized: false, 10 | }:false 11 | }); 12 | 13 | export default connection; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linktr-api", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "dev": "nodemon src/app.js", 10 | "start": "node src/app.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bcrypt": "^5.0.1", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.0", 19 | "express": "^4.17.3", 20 | "joi": "^17.6.0", 21 | "jsonwebtoken": "^8.5.1", 22 | "nodemon": "^2.0.15", 23 | "pg": "^8.7.3", 24 | "string-strip-html": "^9.1.7", 25 | "url-metadata": "^2.5.0", 26 | "uuid": "^8.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import cors from "cors"; 2 | import express, { json } from "express"; 3 | import router from "./routes/index.js"; 4 | import "dotenv/config"; 5 | 6 | const app = express(); 7 | 8 | app.use(cors()); 9 | app.use(json()); 10 | app.use(router); 11 | 12 | export default router; 13 | 14 | app.listen(process.env.PORT, () => { 15 | console.log("Server is listening on port " + process.env.PORT); 16 | }); 17 | -------------------------------------------------------------------------------- /src/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcrypt"; 2 | import { v4 as uuid } from "uuid"; 3 | import { 4 | verifyExistingUser, 5 | createSession, 6 | deleteSession, 7 | } from "../repositories/authRepository.js"; 8 | 9 | export async function signin(req, res) { 10 | const { email, password } = req.body; 11 | try { 12 | const { rows: users } = await verifyExistingUser(email); 13 | const [user] = users; 14 | if (!user) { 15 | return res.sendStatus(401); 16 | } 17 | 18 | if (bcrypt.compareSync(password, user.password)) { 19 | const token = uuid(); 20 | await createSession(token, user); 21 | return res.send({ token: token, user: user }); 22 | } 23 | 24 | res.sendStatus(401); 25 | } catch (error) { 26 | console.log(error); 27 | res.sendStatus(500); 28 | } 29 | } 30 | export async function signout(req, res) { 31 | const { user } = res.locals; 32 | 33 | try { 34 | await deleteSession(user); 35 | res.sendStatus(200); 36 | } catch (error) { 37 | console.log(error); 38 | res.sendStatus(500); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/controllers/commentController.js: -------------------------------------------------------------------------------- 1 | import { fetchComments, postComment } from "../repositories/commentRepository.js"; 2 | 3 | export async function getComments(req, res){ 4 | try{ 5 | const { user } = res.locals; 6 | const { id } = req.params; 7 | const { rows: comments } = await fetchComments(id, user.id) 8 | res.status(200).send(comments); 9 | }catch(error){ 10 | console.log(error); 11 | res.sendStatus(500); 12 | } 13 | } 14 | 15 | export async function postComments(req, res){ 16 | const {user} = res.locals 17 | const { comment } = req.body; 18 | const {id} = req.params; 19 | try{ 20 | await postComment(id, comment, user.id); 21 | res.sendStatus(201); 22 | }catch(error){ 23 | console.log(error); 24 | res.sendStatus(500); 25 | } 26 | } -------------------------------------------------------------------------------- /src/controllers/hashtagsController.js: -------------------------------------------------------------------------------- 1 | import { fetchTendingHashtags } from '../repositories/hashtagRepository.js'; 2 | 3 | export async function getTrendingHashtags(_, res){ 4 | try{ 5 | const { rows: hashtags } = await fetchTendingHashtags() 6 | res.status(200).send(hashtags) 7 | }catch(error){ 8 | console.log(error) 9 | res.sendStatus(500) 10 | } 11 | } -------------------------------------------------------------------------------- /src/controllers/postController.js: -------------------------------------------------------------------------------- 1 | import { 2 | createPost, 3 | getPosts, 4 | createMetaData, 5 | getLastPost, 6 | selectLikeRelation, 7 | updateLikeStatus, 8 | createLikeRelation, 9 | getPostsById, 10 | editPostText, 11 | verifyPostOwner, 12 | getPostsByHashtag, 13 | getLikes, 14 | deleteMetaData, 15 | deleteHashtagsPost, 16 | deleteLikesPost, 17 | deletePost, 18 | deleteShare, 19 | } from "../repositories/postRepository.js"; 20 | import { 21 | getExistingHashtags, 22 | insertHashtags, 23 | insertHashtagsLinksMiddleTable, 24 | getPreviousHashtags, 25 | deleteHashtagsFromMiddleTable, 26 | } from "../repositories/hashtagRepository.js"; 27 | 28 | import { getUserById } from "../repositories/userRepository.js"; 29 | 30 | export async function postLink(req, res) { 31 | const { link, postText } = req.body; 32 | const { user } = res.locals; 33 | const { regex } = res.locals; 34 | 35 | try { 36 | await createPost(link, postText, user.id); 37 | const { rows: lastPost } = await getLastPost(user.id); 38 | await createMetaData(lastPost); 39 | if (regex.length > 0) postHashtags(lastPost[0].id, res); 40 | else return res.sendStatus(201); 41 | } catch (error) { 42 | console.log(error); 43 | return res.sendStatus(500); 44 | } 45 | } 46 | 47 | async function postHashtags(postId, res) { 48 | try { 49 | const { regex } = res.locals; 50 | for (let i = 0; i < regex.length; i++) { 51 | if (i != regex.indexOf(regex[i])) { 52 | regex.splice(i, 1); 53 | i--; 54 | } 55 | } 56 | let str = "WHERE "; 57 | let firstTime = true; 58 | let arr = []; 59 | for (let i = 0; i < regex.length; i++) { 60 | if (firstTime) { 61 | arr.push(regex[0]); 62 | str += `name = $${arr.length}`; 63 | firstTime = false; 64 | } else { 65 | arr.push(regex[i]); 66 | str += ` OR name = $${arr.length}`; 67 | } 68 | } 69 | const { rows: existingHashtags } = await getExistingHashtags(str, arr); 70 | const hashtagsToAdd = [...regex]; 71 | for (let i = 0; i < existingHashtags.length; i++) { 72 | hashtagsToAdd.splice(hashtagsToAdd.indexOf(existingHashtags[i].name), 1); 73 | } 74 | 75 | if (hashtagsToAdd.length > 0) { 76 | str = "VALUES "; 77 | arr = []; 78 | for (let i = 0; i < hashtagsToAdd.length; i++) { 79 | arr.push(hashtagsToAdd[i]); 80 | if (i < hashtagsToAdd.length - 1) { 81 | str += `($${arr.length}), `; 82 | } else { 83 | str += `($${arr.length})`; 84 | } 85 | } 86 | const { rows: newHashtags } = await insertHashtags(str, arr); 87 | const allHashtags = [...existingHashtags, ...newHashtags]; 88 | postHashtagsLinks(postId, allHashtags, res); 89 | } else { 90 | postHashtagsLinks(postId, existingHashtags, res); 91 | } 92 | } catch (error) { 93 | console.log(error); 94 | res.sendStatus(500); 95 | } 96 | } 97 | 98 | async function postHashtagsLinks(postId, hashtags, res) { 99 | try { 100 | let str = "VALUES "; 101 | const arr = []; 102 | for (let i = 0; i < hashtags.length; i++) { 103 | arr.push(hashtags[i].id); 104 | if (i < hashtags.length - 1) { 105 | str += `($${arr.length}, `; 106 | arr.push(postId); 107 | str += `$${arr.length}), `; 108 | } else { 109 | str += `($${arr.length}, `; 110 | arr.push(postId); 111 | str += `$${arr.length})`; 112 | } 113 | } 114 | await insertHashtagsLinksMiddleTable(str, arr); 115 | res.sendStatus(201); 116 | } catch (error) { 117 | console.log(error); 118 | res.sendStatus(500); 119 | } 120 | } 121 | 122 | export async function posts(req, res) { 123 | const { user } = res.locals; 124 | const { offset } = req.params; 125 | const offsetString = `OFFSET ${offset}`; 126 | try { 127 | const result = await getPosts(user, offsetString); 128 | 129 | res.send( 130 | result.rows.map((row) => { 131 | const [ 132 | id, 133 | link, 134 | postText, 135 | userId, 136 | mtPostId, 137 | url, 138 | title, 139 | description, 140 | image, 141 | reposterName, 142 | reposterId, 143 | userName, 144 | userOwnerId, 145 | userImage, 146 | isLike, 147 | numberReposts, 148 | ] = row; 149 | 150 | return { 151 | id, 152 | link, 153 | postText, 154 | userId, 155 | reposterName, 156 | reposterId, 157 | metadata: { url, title, description, image }, 158 | userName, 159 | userImage, 160 | isLike: isLike, 161 | numberReposts, 162 | }; 163 | }) 164 | ); 165 | } catch (error) { 166 | console.log(error); 167 | res.sendStatus(500); 168 | } 169 | } 170 | export async function likePost(req, res) { 171 | let { id, status } = req.params; 172 | const { user } = res.locals; 173 | 174 | status == "true" ? (status = true) : (status = false); 175 | 176 | try { 177 | const { rows: likeRelations } = await selectLikeRelation(id, user); 178 | const [likeRelation] = likeRelations; 179 | if (!likeRelation) { 180 | createLikeRelation(id, user, status); 181 | } else { 182 | await updateLikeStatus(id, user, status); 183 | } 184 | 185 | res.sendStatus(200); 186 | } catch (error) { 187 | console.log(error); 188 | res.sendStatus(500); 189 | } 190 | } 191 | 192 | export async function postsById(req, res) { 193 | const { id } = req.params; 194 | const { user } = res.locals; 195 | try { 196 | const result = await getPostsById(user.id, id); 197 | const { rows: data } = await getUserById(id); 198 | const userData = data[0]; 199 | const answer = result.rows.map((row) => { 200 | const [ 201 | id, 202 | link, 203 | postText, 204 | userId, 205 | mtPostId, 206 | url, 207 | title, 208 | description, 209 | image, 210 | reposterName, 211 | reposterId, 212 | userName, 213 | userOwnerId, 214 | userImage, 215 | isLike, 216 | numberReposts, 217 | ] = row; 218 | 219 | return { 220 | id, 221 | link, 222 | postText, 223 | userId, 224 | reposterName, 225 | reposterId, 226 | metadata: { url, title, description, image }, 227 | userName, 228 | userImage, 229 | isLike: isLike, 230 | numberReposts, 231 | }; 232 | }); 233 | res.send({ answer, userData }); 234 | } catch (error) { 235 | console.log(error); 236 | res.sendStatus(500); 237 | } 238 | } 239 | 240 | export async function postsByHashtag(req, res) { 241 | const { user } = res.locals; 242 | let { name: hashtag } = req.params; 243 | hashtag.trim(); 244 | hashtag = `#${hashtag}`; 245 | try { 246 | const result = await getPostsByHashtag(hashtag, user); 247 | 248 | res.send( 249 | result.rows.map((row) => { 250 | const [ 251 | id, 252 | link, 253 | postText, 254 | userId, 255 | mtPostId, 256 | postId, 257 | url, 258 | title, 259 | description, 260 | image, 261 | userName, 262 | userImage, 263 | isLike, 264 | hashtagId, 265 | , 266 | numberReposts, 267 | ] = row; 268 | 269 | return { 270 | id, 271 | link, 272 | postText, 273 | userId, 274 | metadata: { url, title, description, image }, 275 | userName, 276 | userImage, 277 | isLike, 278 | numberReposts, 279 | }; 280 | }) 281 | ); 282 | } catch (error) { 283 | console.log(error); 284 | res.sendStatus(500); 285 | } 286 | } 287 | 288 | export async function getAllLikes(req, res) { 289 | const { id } = req.params; 290 | const { rows: peopleLikes } = await getLikes(id); 291 | res.send(peopleLikes); 292 | } 293 | 294 | export async function deletePosts(req, res) { 295 | const { id } = req.params; 296 | try { 297 | await deleteMetaData(id); 298 | await deleteHashtagsPost(id); 299 | await deleteLikesPost(id); 300 | await deleteShare(id); 301 | await deletePost(id); 302 | res.sendStatus(200); 303 | } catch (error) { 304 | console.log(error); 305 | res.sendStatus(500); 306 | } 307 | } 308 | 309 | export async function editPost(req, res) { 310 | try { 311 | const user = res.locals.user; 312 | const verified = await verifyPostOwner(user.id, req.params.id); 313 | if (verified.rowCount < 1) { 314 | return res.sendStatus(401); 315 | } 316 | const { regex } = res.locals; 317 | for (let i = 0; i < regex.length; i++) { 318 | if (i != regex.indexOf(regex[i])) { 319 | regex.splice(i, 1); 320 | i--; 321 | } 322 | } 323 | const { rows: lastHashtags } = await getPreviousHashtags(req.params.id); 324 | 325 | const hashtagsToAdd = [...regex]; 326 | for (let i = 0; i < lastHashtags.length; i++) { 327 | if (hashtagsToAdd.includes(lastHashtags[i].name)) { 328 | hashtagsToAdd.splice(hashtagsToAdd.indexOf(lastHashtags[i].name), 1); 329 | } 330 | } 331 | 332 | let str = "WHERE "; 333 | let firstTime = true; 334 | let arr = []; 335 | for (let i = 0; i < hashtagsToAdd.length; i++) { 336 | if (firstTime) { 337 | arr.push(hashtagsToAdd[0]); 338 | str += `name = $${arr.length}`; 339 | firstTime = false; 340 | } else { 341 | arr.push(hashtagsToAdd[i]); 342 | str += ` OR name = $${arr.length}`; 343 | } 344 | } 345 | 346 | if (hashtagsToAdd.length > 0) { 347 | const { rows: existingHashtags } = await getExistingHashtags(str, arr); 348 | 349 | const hashtagsToAddMiddleOnly = []; 350 | for (let i = 0; i < existingHashtags.length; i++) { 351 | hashtagsToAddMiddleOnly.push(existingHashtags[i].name); 352 | } 353 | 354 | const hashtagsToAddToDbAndMiddle = []; 355 | for (let i = 0; i < hashtagsToAdd.length; i++) { 356 | if (!hashtagsToAddMiddleOnly.includes(hashtagsToAdd[i])) { 357 | hashtagsToAddToDbAndMiddle.push(hashtagsToAdd[i]); 358 | } 359 | } 360 | 361 | let finalHashtags = [...existingHashtags]; 362 | if (hashtagsToAddToDbAndMiddle.length > 0) { 363 | str = "VALUES "; 364 | arr = []; 365 | for (let i = 0; i < hashtagsToAddToDbAndMiddle.length; i++) { 366 | arr.push(hashtagsToAddToDbAndMiddle[i]); 367 | if (i < hashtagsToAddToDbAndMiddle.length - 1) { 368 | str += `($${arr.length}), `; 369 | } else { 370 | str += `($${arr.length})`; 371 | } 372 | } 373 | 374 | const { rows: newHashtags } = await insertHashtags(str, arr); 375 | finalHashtags = [...finalHashtags, ...newHashtags]; 376 | } 377 | 378 | str = "VALUES "; 379 | arr = []; 380 | for (let i = 0; i < finalHashtags.length; i++) { 381 | arr.push(finalHashtags[i].id); 382 | if (i < finalHashtags.length - 1) { 383 | str += `($${arr.length}, `; 384 | arr.push(parseInt(req.params.id)); 385 | str += `$${arr.length}), `; 386 | } else { 387 | str += `($${arr.length}, `; 388 | arr.push(parseInt(req.params.id)); 389 | str += `$${arr.length})`; 390 | } 391 | } 392 | await insertHashtagsLinksMiddleTable(str, arr); 393 | } 394 | 395 | const hashtagsToRemove = []; 396 | for (let i = 0; i < lastHashtags.length; i++) { 397 | if (!regex.includes(lastHashtags[i].name)) { 398 | hashtagsToRemove.push(lastHashtags[i]); 399 | } 400 | } 401 | 402 | if (hashtagsToRemove.length > 0) { 403 | str = "("; 404 | arr = []; 405 | firstTime = true; 406 | for (let i = 0; i < hashtagsToRemove.length; i++) { 407 | if (i == hashtagsToRemove.length - 1) { 408 | arr.push(hashtagsToRemove[i].hashtagId); 409 | str += `$${arr.length})`; 410 | } else { 411 | arr.push(hashtagsToRemove[i].hashtagId); 412 | str += `$${arr.length}, `; 413 | } 414 | } 415 | await deleteHashtagsFromMiddleTable(str, arr, req.params.id); 416 | } 417 | 418 | await editPostText(req.body.postText, req.params.id); 419 | 420 | res.sendStatus(200); 421 | } catch (error) { 422 | console.log(error); 423 | res.sendStatus(500); 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/controllers/repostController.js: -------------------------------------------------------------------------------- 1 | 2 | import { createRepost, deleteRepost, verifyRepost } from "../repositories/repostRepository.js"; 3 | 4 | 5 | export async function createReposts(req, res){ 6 | const {user} = res.locals 7 | const {id} = req.params 8 | 9 | try { 10 | const {rows : [verifyAlreadyRepost]} = await verifyRepost(id, user.id) 11 | if(verifyAlreadyRepost){ 12 | await deleteRepost(id, user.id) 13 | return res.sendStatus(200) 14 | } 15 | await createRepost(id, user.id) 16 | res.sendStatus(201) 17 | } catch (error) { 18 | console.log(error); 19 | res.sendStatus(500); 20 | } 21 | 22 | } 23 | 24 | 25 | export async function verifyAlreadyRepost(req, res){ 26 | const {user} = res.locals 27 | const {id} = req.params 28 | 29 | try { 30 | const {rows : [verifyAlreadyRepost]} = await verifyRepost(id, user.id) 31 | if(verifyAlreadyRepost){ 32 | return res.send(true) 33 | 34 | } 35 | res.send(false) 36 | } catch (error) { 37 | console.log(error); 38 | res.sendStatus(500); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/controllers/searchBarController.js: -------------------------------------------------------------------------------- 1 | import { getUsersToSearchBar } from "../repositories/searchBarRepository.js"; 2 | 3 | export async function getSearchBar(req, res) { 4 | const { name } = req.params; 5 | const { user } = res.locals; 6 | try { 7 | const result = await getUsersToSearchBar(name, user.id); 8 | res.send(result.rows); 9 | return result.rows; 10 | } catch (error) { 11 | console.log(error); 12 | res.sendStatus(500); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/controllers/usersController.js: -------------------------------------------------------------------------------- 1 | import { verifyExistingUser } from "../repositories/authRepository.js"; 2 | import { 3 | addUser, 4 | followUserById, 5 | verifyFollower, 6 | unfollowUser, 7 | checkFollowing, 8 | } from "../repositories/userRepository.js"; 9 | import bcrypt from "bcrypt"; 10 | 11 | export async function createUser(req, res) { 12 | const user = req.body; 13 | try { 14 | const existingUsers = await verifyExistingUser(user.email); 15 | if (existingUsers.rowCount > 0) { 16 | return res.sendStatus(409); 17 | } 18 | 19 | const passwordHash = bcrypt.hashSync(user.password, 10); 20 | 21 | await addUser(user, passwordHash); 22 | res.sendStatus(201); 23 | } catch (error) { 24 | console.log(error); 25 | return res.sendStatus(500); 26 | } 27 | } 28 | 29 | export async function getUser(req, res) { 30 | const { user } = res.locals; 31 | 32 | res.send({ id: user.id, image: user.image, name: user.name }); 33 | } 34 | 35 | export async function followUser(req, res) { 36 | const { user } = res.locals; 37 | const { followedUserId } = req.body; 38 | try { 39 | const { rows: followers } = await verifyFollower(followedUserId, user.id); 40 | const follower = followers[0]; 41 | if (follower) { 42 | await unfollowUser(followedUserId, user.id); 43 | return res.sendStatus(200); 44 | } 45 | await followUserById(followedUserId, user.id); 46 | res.sendStatus(201); 47 | } catch (error) { 48 | console.log(error); 49 | res.sendStatus(500); 50 | } 51 | } 52 | 53 | export async function getFollower(req, res) { 54 | const { user } = res.locals; 55 | const { id } = req.params; 56 | 57 | try { 58 | const { rows: followers } = await verifyFollower(id, user.id); 59 | res.send(followers); 60 | } catch (error) { 61 | console.log(error); 62 | res.sendStatus(500); 63 | } 64 | } 65 | export async function checkFollowings(req, res) { 66 | const { user } = res.locals; 67 | 68 | try { 69 | const { rows: result } = await checkFollowing(user.id); 70 | res.send(result[0]); 71 | } catch (error) { 72 | console.log(error); 73 | res.sendStatus(500); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/middlewares/validateHashtagMiddleware.js: -------------------------------------------------------------------------------- 1 | export default function hashtagsRegex(req, res, next){ 2 | const {postText} = req.body 3 | 4 | const regexp = /#+[a-zA-Z0-9A-Za-zÀ-ÖØ-öø-ʸ(_)]{1,}/g; 5 | 6 | const array = [...postText.matchAll(regexp)]; 7 | 8 | for(let i = 0; i < array.length; i++){ 9 | array[i] = array[i][0] 10 | } 11 | 12 | res.locals.regex = array 13 | 14 | next() 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/middlewares/validateSchemaMiddleware.js: -------------------------------------------------------------------------------- 1 | export default function validateSchemaMiddleware(schema) { 2 | return (req, res, next) => { 3 | const validation = schema.validate(req.body); 4 | if (validation.error) { 5 | console.log(validation.error.message); 6 | return res.status(422).send(validation.error.message); 7 | } 8 | next(); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/middlewares/validateTokenMiddleware.js: -------------------------------------------------------------------------------- 1 | import { getSession, getUserSession } from "../repositories/authRepository.js"; 2 | 3 | export async function validateTokenMiddleware(req, res, next) { 4 | const authorization = req.headers.authorization; 5 | const token = authorization?.replace("Bearer ", ""); 6 | 7 | if (!token) { 8 | return res.sendStatus(401); 9 | } 10 | 11 | const { rows: sessions } = await getSession(token); 12 | const [session] = sessions; 13 | 14 | if (!session) { 15 | return res.sendStatus(401); 16 | } 17 | 18 | const { rows: users } = await getUserSession(session); 19 | const [user] = users; 20 | 21 | if (!user) { 22 | return res.sendStatus(401); 23 | } 24 | 25 | res.locals.user = user; 26 | next(); 27 | } 28 | -------------------------------------------------------------------------------- /src/repositories/authRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | export async function verifyExistingUser(email) { 4 | return connection.query("SELECT * FROM users WHERE email=$1", [email]); 5 | } 6 | 7 | export async function createSession(token, user) { 8 | return connection.query( 9 | 'INSERT INTO sessions (token, "userId") VALUES ($1, $2)', 10 | [token, user.id] 11 | ); 12 | } 13 | export async function deleteSession(user) { 14 | return connection.query('DELETE FROM sessions WHERE "userId"=$1', [user.id]); 15 | } 16 | 17 | export async function getSession(token) { 18 | return connection.query(`SELECT * FROM sessions WHERE token=$1`, [token]); 19 | } 20 | 21 | export async function getUserSession(session) { 22 | return connection.query(`SELECT * FROM users WHERE id=$1`, [session.userId]); 23 | } 24 | -------------------------------------------------------------------------------- /src/repositories/commentRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | export async function fetchComments(postId, userId){ 4 | return connection.query(` 5 | SELECT comments.*, users.name, users.image, followers."userId" as following 6 | FROM comments 7 | JOIN users ON comments."userId"=users.id 8 | LEFT JOIN followers ON followers."followedByUserId"=$2 AND followers."userId"=comments."userId" 9 | WHERE comments."postId"=$1 10 | ORDER BY comments.id DESC 11 | `, [postId, userId]) 12 | } 13 | 14 | // export async function getNotFollowers(name, user){ 15 | // return connection.query(` 16 | // SELECT u.id, u.name, u.image FROM users u 17 | // LEFT JOIN "followers" f ON f."userId" = u.id 18 | // WHERE name like $1 19 | // GROUP BY f."followedByUserId" = $2, u.id 20 | // `,[name, user]); 21 | // } 22 | 23 | export async function postComment(postId, comment, user){ 24 | return connection.query(` 25 | INSERT INTO 26 | "comments" ("postId", comment, "userId") 27 | VALUES ($1, $2, $3) 28 | `,[postId, comment, user]); 29 | } -------------------------------------------------------------------------------- /src/repositories/hashtagRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | export async function getExistingHashtags(str, arr){ 4 | return connection.query(` 5 | SELECT * FROM hashtags ${str} 6 | `,arr); 7 | 8 | } 9 | 10 | export async function insertHashtags(str, arr){ 11 | return connection.query(` 12 | INSERT INTO hashtags (name) 13 | ${str} 14 | RETURNING * 15 | `, arr) 16 | } 17 | 18 | export async function insertHashtagsLinksMiddleTable(str, arr){ 19 | return connection.query(` 20 | INSERT INTO "hashtagsPosts" ("hashtagId", "postId") 21 | ${str} 22 | `, arr) 23 | } 24 | 25 | export async function getPreviousHashtags(id){ 26 | return connection.query(` 27 | SELECT "hashtagId", name 28 | FROM "hashtagsPosts" 29 | JOIN hashtags ON "hashtagsPosts"."hashtagId"=hashtags.id 30 | WHERE "hashtagsPosts"."postId"=$1 31 | `,[id]) 32 | } 33 | 34 | export async function deleteHashtagsFromMiddleTable(str, arr, id){ 35 | return connection.query(` 36 | DELETE FROM "hashtagsPosts" 37 | WHERE "hashtagId" IN ${str} 38 | AND "postId" IN ($${arr.length + 1}) 39 | `, [...arr, id]) 40 | } 41 | 42 | export async function fetchTendingHashtags(){ 43 | return connection.query(` 44 | SELECT COUNT("hashtagId") as uses, name 45 | FROM "hashtagsPosts" 46 | JOIN hashtags ON "hashtagId"=hashtags.id 47 | GROUP BY name 48 | ORDER BY uses DESC 49 | LIMIT 10 50 | `) 51 | } -------------------------------------------------------------------------------- /src/repositories/postRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | import urlMetadata from "url-metadata"; 3 | 4 | export async function createPost(link, postText, id) { 5 | return connection.query( 6 | ` 7 | INSERT INTO 8 | posts (link, "postText", "userId") 9 | VALUES ($1, $2, $3) 10 | RETURNING * 11 | `, 12 | [link, postText, id] 13 | ); 14 | } 15 | 16 | export async function createMetaData([post]) { 17 | try { 18 | const metadata = await urlMetadata(post.link); 19 | 20 | return connection.query( 21 | ` 22 | INSERT INTO 23 | "metaData" ("postId", url, title, description, image) 24 | VALUES ($1, $2, $3, $4, $5) 25 | 26 | `, 27 | [ 28 | post.id, 29 | metadata.url, 30 | metadata.title, 31 | metadata.description, 32 | metadata.image, 33 | ] 34 | ); 35 | } catch (error) { 36 | console.log(error); 37 | } 38 | } 39 | 40 | export async function getLastPost(id) { 41 | return connection.query( 42 | `SELECT * 43 | FROM posts 44 | WHERE posts."userId"=$1 45 | ORDER BY posts.id DESC 46 | LIMIT 1`, 47 | [id] 48 | ); 49 | } 50 | //LEFT JOIN shares ON shares."postId"=posts.id 51 | 52 | export async function getPosts(user, offset) { 53 | return connection.query( 54 | { 55 | text: ` 56 | SELECT * FROM ( 57 | SELECT 58 | pt.*, 59 | mt."postId", mt.url, mt.title, mt.description, mt.image, 60 | null as userNameRepost, 61 | null as userIdRepost, 62 | upost.name as userNamePost, 63 | upost.id as userNamePostId, 64 | upost.image as "userImage", 65 | "likesPosts".like, 66 | ( select Count(*) from shares where shares."postId" = pt.id) as "numberReposts" 67 | from 68 | posts pt 69 | join "metaData" mt on 70 | pt.id = mt."postId" 71 | join users upost on 72 | upost.id = pt."userId" 73 | LEFT JOIN "likesPosts" ON pt.id = "likesPosts"."postId" AND "likesPosts"."userId"= $1 74 | where 75 | pt."userId" in ( 76 | select 77 | "userId" 78 | from 79 | followers f 80 | where 81 | f."followedByUserId" = $1 82 | ) 83 | UNION ALL 84 | SELECT 85 | pt.*, 86 | mt."postId", mt.url, mt.title, mt.description, mt.image, 87 | uRepost.name as userNameRepost, 88 | uRepost.id as userIdRepost, 89 | upost.name as userNamePost, 90 | upost.id as userNamePostId, 91 | upost.image as "userImage", 92 | "likesPosts".like, 93 | ( select Count(*) from shares where shares."postId" = s."postId") as "numberReposts" 94 | from 95 | shares s 96 | join posts pt on 97 | pt.id = s."postId" 98 | join users uRepost 99 | on 100 | uRepost.id = s."userId" 101 | join users upost 102 | on 103 | upost.id = pt."userId" 104 | join "metaData" mt on 105 | s."postId" = mt."postId" 106 | LEFT JOIN "likesPosts" ON s."postId" = "likesPosts"."postId" AND "likesPosts"."userId"= $1 107 | where 108 | s."userId" in ( 109 | select 110 | "userId" 111 | from 112 | followers f 113 | where 114 | f."followedByUserId" = $1 115 | ) 116 | ) "mainTable" 117 | ORDER BY "mainTable"."id" DESC 118 | LIMIT 10 119 | ${offset} 120 | 121 | `, 122 | rowMode: "array", 123 | }, 124 | [user.id] 125 | ); 126 | } 127 | 128 | export async function getLikes(postId) { 129 | return connection.query( 130 | `SELECT posts.id, "likesPosts".like, users.name 131 | FROM "likesPosts" 132 | LEFT JOIN users 133 | ON users.id="likesPosts"."userId" 134 | LEFT JOIN posts 135 | ON posts.id="likesPosts"."postId" 136 | WHERE "likesPosts".like='t' AND posts.id=$1 137 | GROUP BY users.name, posts.id, "likesPosts".like 138 | `, 139 | [postId] 140 | ); 141 | } 142 | 143 | export async function createLikeRelation(id, user, status) { 144 | return connection.query( 145 | ` 146 | INSERT INTO "likesPosts" ("userId","postId","like") VALUES ($1,$2,$3) 147 | `, 148 | [user.id, id, status] 149 | ); 150 | } 151 | 152 | export async function selectLikeRelation(id, user) { 153 | return connection.query( 154 | ` 155 | SELECT "likesPosts".*, posts.id FROM posts 156 | JOIN "likesPosts" 157 | ON posts.id="likesPosts"."postId" AND "likesPosts"."userId"=$1 158 | WHERE posts.id=$2`, 159 | [user.id, id] 160 | ); 161 | } 162 | 163 | export async function updateLikeStatus(id, user, status) { 164 | return connection.query( 165 | ` 166 | UPDATE "likesPosts" 167 | SET "like"=$1 168 | WHERE "likesPosts"."postId"=$2 AND "likesPosts"."userId"=$3 169 | `, 170 | [status, id, user.id] 171 | ); 172 | } 173 | 174 | export async function getPostsById(userId, id) { 175 | return connection.query( 176 | { 177 | text: ` SELECT * FROM ( 178 | SELECT 179 | pt.*, 180 | mt."postId", mt.url, mt.title, mt.description, mt.image, 181 | null as userNameRepost, 182 | null as userIdRepost, 183 | upost.name as userNamePost, 184 | upost.id as userNamePostId, 185 | upost.image as "userImage", 186 | "likesPosts".like, 187 | ( select Count(*) from shares where shares."postId" = pt.id) as "numberReposts" 188 | from 189 | posts pt 190 | join "metaData" mt on 191 | pt.id = mt."postId" 192 | join users upost on 193 | upost.id = pt."userId" 194 | LEFT JOIN "likesPosts" ON pt.id = "likesPosts"."postId" AND "likesPosts"."userId"= $1 195 | WHERE upost.id=$2 196 | UNION ALL 197 | SELECT 198 | pt.*, 199 | mt."postId", mt.url, mt.title, mt.description, mt.image, 200 | uRepost.name as userNameRepost, 201 | uRepost.id as userIdRepost, 202 | upost.name as userNamePost, 203 | upost.id as userNamePostId, 204 | upost.image as "userImage", 205 | "likesPosts".like, 206 | ( select Count(*) from shares where shares."postId" = s."postId") as "numberReposts" 207 | from 208 | shares s 209 | join posts pt on 210 | pt.id = s."postId" 211 | join users uRepost 212 | on 213 | uRepost.id = s."userId" 214 | join users upost 215 | on 216 | upost.id = pt."userId" 217 | join "metaData" mt on 218 | s."postId" = mt."postId" 219 | LEFT JOIN "likesPosts" ON s."postId" = "likesPosts"."postId" AND "likesPosts"."userId"= $1 220 | WHERE uRepost.id=$2 221 | ) "mainTable" 222 | ORDER BY "mainTable"."id" DESC 223 | `, 224 | rowMode: "array", 225 | }, 226 | [userId, id] 227 | ); 228 | } 229 | 230 | export async function getPostsByHashtag(hashtag, user) { 231 | return connection.query( 232 | { 233 | text: `SELECT posts.*, "metaData".*, users.name, users.image AS "userImage","likesPosts".like,hashtags.id,"hashtagsPosts".id, (SELECT Count(*) FROM shares WHERE shares."postId" = posts.id) AS "numberReposts" 234 | FROM posts 235 | JOIN "metaData" 236 | ON posts.id="metaData"."postId" 237 | JOIN users 238 | ON posts."userId"=users.id 239 | JOIN "hashtagsPosts" ON "hashtagsPosts"."postId"=posts.id 240 | JOIN hashtags ON hashtags.id="hashtagsPosts"."hashtagId" AND hashtags.name ILIKE $1 241 | LEFT JOIN "likesPosts" 242 | ON posts.id="likesPosts"."postId" and "likesPosts"."userId"=$2 243 | ORDER BY posts.id DESC 244 | LIMIT 20`, 245 | rowMode: "array", 246 | }, 247 | [hashtag, user.id] 248 | ); 249 | } 250 | 251 | export async function editPostText(txt, id) { 252 | return connection.query( 253 | ` 254 | UPDATE "posts" 255 | SET "postText" = $1 256 | WHERE "id" = $2 257 | `, 258 | [txt, id] 259 | ); 260 | } 261 | 262 | export async function verifyPostOwner(userId, postId) { 263 | return connection.query( 264 | ` 265 | SELECT * FROM "posts" 266 | WHERE "userId" = $1 267 | AND id = $2 268 | `, 269 | [userId, postId] 270 | ); 271 | } 272 | 273 | export async function deleteMetaData(id) { 274 | return connection.query( 275 | ` 276 | DELETE FROM "metaData" WHERE "postId" = $1 277 | `, 278 | [id] 279 | ); 280 | } 281 | 282 | export async function deleteHashtagsPost(id) { 283 | return connection.query( 284 | ` 285 | DELETE FROM "hashtagsPosts" WHERE "postId" = $1 286 | `, 287 | [id] 288 | ); 289 | } 290 | 291 | export async function deleteLikesPost(id) { 292 | return connection.query( 293 | ` 294 | DELETE FROM "likesPosts" WHERE "postId" = $1 295 | `, 296 | [id] 297 | ); 298 | } 299 | export async function deleteShare(id) { 300 | return connection.query( 301 | ` 302 | DELETE FROM shares WHERE "postId" = $1 303 | `, 304 | [id] 305 | ); 306 | } 307 | 308 | export async function deletePost(id) { 309 | return connection.query( 310 | ` 311 | DELETE FROM posts WHERE id = $1 312 | `, 313 | [id] 314 | ); 315 | } 316 | -------------------------------------------------------------------------------- /src/repositories/repostRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | 4 | export async function createRepost(postId, userId) { 5 | return connection.query( 6 | ` 7 | INSERT INTO 8 | shares ("postId", "userId", "userName") 9 | VALUES ($1, $2, (SELECT users.name from users where users.id=$2)) 10 | `, 11 | [postId, userId] 12 | ); 13 | } 14 | 15 | export async function verifyRepost(postId, userId){ 16 | return connection.query( 17 | ` 18 | SELECT * 19 | FROM 20 | shares 21 | WHERE "postId"=$1 AND "userId"=$2 22 | `, 23 | [postId, userId] 24 | ); 25 | } 26 | export async function deleteRepost(postId, userId){ 27 | return connection.query( 28 | ` 29 | DELETE 30 | FROM shares 31 | WHERE "postId"=$1 AND "userId"=$2 32 | `, 33 | [postId, userId] 34 | ); 35 | } 36 | 37 | 38 | 39 | export async function numberReposts([postId]){ 40 | return connection.query(` 41 | SELECT COUNT(*) AS "numberReposts", 42 | FROM shares 43 | WHERE shares."postId"=$1 44 | GROUP BY "numberReposts" 45 | `,[postId]) 46 | } -------------------------------------------------------------------------------- /src/repositories/searchBarRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | export async function getUsersToSearchBar(name, user) { 4 | return connection.query( 5 | ` 6 | 7 | SELECT u.id, u.name, u.image, f."followedByUserId" AS "followed" FROM users u 8 | LEFT JOIN "followers" f ON f."userId" = u.id AND f."followedByUserId" = $2 9 | WHERE name ilike $1 10 | ORDER BY "followed" 11 | `, 12 | [`${name}%`, user] 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/repositories/userRepository.js: -------------------------------------------------------------------------------- 1 | import connection from "../../db.js"; 2 | 3 | export async function addUser(user, passwordHash) { 4 | return connection.query( 5 | ` 6 | INSERT INTO 7 | users (name, email, password ,image) 8 | VALUES ($1, $2, $3,$4) 9 | `, 10 | [user.username, user.email, passwordHash, user.image] 11 | ); 12 | } 13 | 14 | export async function followUserById(idFollowed, idFollower) { 15 | return connection.query( 16 | ` 17 | INSERT INTO followers("userId","followedByUserId") VALUES($1,$2) 18 | `, 19 | [idFollowed, idFollower] 20 | ); 21 | } 22 | export async function unfollowUser(idFollowed, idFollower) { 23 | return connection.query( 24 | ` 25 | DELETE FROM followers WHERE "userId"=$1 AND "followedByUserId"=$2 26 | `, 27 | [idFollowed, idFollower] 28 | ); 29 | } 30 | 31 | export async function verifyFollower(idFollowed, idFollower) { 32 | return connection.query( 33 | ` 34 | SELECT id FROM followers WHERE "userId"=$1 AND "followedByUserId"=$2 35 | `, 36 | [idFollowed, idFollower] 37 | ); 38 | } 39 | 40 | export async function checkFollowing(id) { 41 | return connection.query( 42 | `SELECT id FROM followers WHERE "followedByUserId"=$1`, 43 | [id] 44 | ); 45 | } 46 | export async function getUserById(id) { 47 | return connection.query( 48 | `SELECT id, name AS "userName", users.image AS "userImage" FROM users WHERE id=$1`, 49 | [id] 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/routes/authRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { signin, signout } from "../controllers/authController.js"; 3 | import validateSchemaMiddleware from "../middlewares/validateSchemaMiddleware.js"; 4 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js"; 5 | import signinSchema from "../schemas/signinSchema.js"; 6 | 7 | const authRouter = Router(); 8 | authRouter.post("/signin", validateSchemaMiddleware(signinSchema), signin); 9 | authRouter.delete("/signout", validateTokenMiddleware, signout); 10 | export default authRouter; 11 | -------------------------------------------------------------------------------- /src/routes/commentRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { getComments, postComments } from "../controllers/commentController.js"; 3 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js" 4 | import validateSchemaMiddleware from "../middlewares/validateSchemaMiddleware.js"; 5 | import commentSchema from "../schemas/commentSchema.js"; 6 | const commentRouter = Router(); 7 | 8 | commentRouter.get("/comments/:id", validateTokenMiddleware, getComments); 9 | commentRouter.post("/comments/:id", validateTokenMiddleware,validateSchemaMiddleware(commentSchema),postComments); 10 | 11 | export default commentRouter; -------------------------------------------------------------------------------- /src/routes/hashtagsRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { getTrendingHashtags } from "../controllers/hashtagsController.js"; 3 | 4 | const hashtagsRouter = Router(); 5 | 6 | hashtagsRouter.get("/hashtags", getTrendingHashtags); 7 | 8 | export default hashtagsRouter; 9 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import authRouter from "./authRouter.js"; 3 | import userRouter from "./userRouter.js"; 4 | import postRouter from "./postRouter.js"; 5 | import hashtagsRouter from "./hashtagsRouter.js"; 6 | import searchBarRouter from "./searchBarRouter.js"; 7 | import commentRouter from "./commentRouter.js"; 8 | import repostRouter from "./repostRouter.js"; 9 | 10 | const router = Router(); 11 | router.use(authRouter); 12 | router.use(userRouter); 13 | router.use(postRouter); 14 | router.use(hashtagsRouter); 15 | router.use(searchBarRouter); 16 | router.use(commentRouter); 17 | router.use(repostRouter) 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routes/postRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | getAllLikes, 4 | likePost, 5 | postLink, 6 | posts, 7 | postsByHashtag, 8 | postsById, 9 | editPost, 10 | deletePosts 11 | } from "../controllers/postController.js"; 12 | import postSchema from "../schemas/postSchema.js"; 13 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js"; 14 | import validateSchemaMiddleware from "../middlewares/validateSchemaMiddleware.js"; 15 | import hashtagsRegex from "../middlewares/validateHashtagMiddleware.js"; 16 | 17 | 18 | const postRouter = Router(); 19 | 20 | postRouter.get("/timeline/:offset?", validateTokenMiddleware, posts); 21 | 22 | postRouter.post( 23 | "/timeline", 24 | validateTokenMiddleware, 25 | validateSchemaMiddleware(postSchema), 26 | hashtagsRegex, 27 | postLink 28 | ); 29 | 30 | postRouter.put("/posts/:id/:status", validateTokenMiddleware, likePost); 31 | postRouter.get( 32 | "/posts/hashtags/:name", 33 | validateTokenMiddleware, 34 | postsByHashtag 35 | ); 36 | postRouter.get("/user/:id", validateTokenMiddleware, postsById); 37 | postRouter.delete("/deletepost/:id", validateTokenMiddleware, deletePosts); 38 | 39 | 40 | postRouter.patch("/posts/edit/:id", validateTokenMiddleware, validateSchemaMiddleware(postSchema), hashtagsRegex, editPost) 41 | postRouter.get("/likes/:id", getAllLikes) 42 | 43 | export default postRouter; 44 | -------------------------------------------------------------------------------- /src/routes/repostRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { createReposts, verifyAlreadyRepost } from "../controllers/repostController.js"; 3 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js"; 4 | 5 | 6 | const repostRouter = Router(); 7 | 8 | repostRouter.post("/repost/:id", validateTokenMiddleware, createReposts); 9 | /* repostRouter.get("/repost/:offset", validateTokenMiddleware, reposts) */ 10 | repostRouter.get("/repost/:id", validateTokenMiddleware, verifyAlreadyRepost) 11 | export default repostRouter; -------------------------------------------------------------------------------- /src/routes/searchBarRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js"; 3 | import { getSearchBar } from "../controllers/searchBarController.js"; 4 | 5 | const searchBarRouter = Router(); 6 | searchBarRouter.get("/searchbar/:name", validateTokenMiddleware, getSearchBar); 7 | 8 | 9 | export default searchBarRouter; 10 | -------------------------------------------------------------------------------- /src/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | checkFollowings, 4 | createUser, 5 | followUser, 6 | getFollower, 7 | getUser, 8 | } from "../controllers/usersController.js"; 9 | import validateSchemaMiddleware from "../middlewares/validateSchemaMiddleware.js"; 10 | import { validateTokenMiddleware } from "../middlewares/validateTokenMiddleware.js"; 11 | import followSchema from "../schemas/followSchema.js"; 12 | import signupSchema from "../schemas/signupSchema.js"; 13 | 14 | const userRouter = Router(); 15 | userRouter.post("/signup", validateSchemaMiddleware(signupSchema), createUser); 16 | userRouter.get("/users", validateTokenMiddleware, getUser); 17 | userRouter.post( 18 | "/users/follow", 19 | validateTokenMiddleware, 20 | validateSchemaMiddleware(followSchema), 21 | followUser 22 | ); 23 | userRouter.get("/users/follow/:id", validateTokenMiddleware, getFollower); 24 | userRouter.get("/users/following", validateTokenMiddleware, checkFollowings); 25 | export default userRouter; 26 | -------------------------------------------------------------------------------- /src/schemas/commentSchema.js: -------------------------------------------------------------------------------- 1 | import joi from "joi"; 2 | 3 | const commentSchema = joi.object({ 4 | comment: joi.string().required(), 5 | }); 6 | 7 | export default commentSchema; 8 | -------------------------------------------------------------------------------- /src/schemas/followSchema.js: -------------------------------------------------------------------------------- 1 | import joi from "joi"; 2 | 3 | const followSchema = joi.object({ 4 | followedUserId: joi.required(), 5 | }); 6 | 7 | export default followSchema; 8 | -------------------------------------------------------------------------------- /src/schemas/postSchema.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const postSchema = Joi.object({ 4 | link: Joi.string().uri().required(), 5 | postText: Joi.string() 6 | }); 7 | 8 | export default postSchema; 9 | -------------------------------------------------------------------------------- /src/schemas/signinSchema.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const signinSchema = Joi.object({ 4 | email: Joi.string().email().required(), 5 | password: Joi.string().required(), 6 | }); 7 | 8 | export default signinSchema; 9 | -------------------------------------------------------------------------------- /src/schemas/signupSchema.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const signupSchema = Joi.object({ 4 | email: Joi.string().email().required(), 5 | password: Joi.string().required(), 6 | username: Joi.string().required(), 7 | image: Joi.string() 8 | .uri() 9 | .pattern(/(https?:\/\/.*\.(?:png|jpg|jpeg|jfif|gif))/i) 10 | .required(), 11 | }); 12 | 13 | export default signupSchema; 14 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blarth/linkr-api/14a6d7cbf6ed73080eb07a7a497d7a1295175046/src/utils/index.js --------------------------------------------------------------------------------