Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions Database/db.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
DROP TABLE IF EXISTS users;
CREATE TABLE users (
CREATE EXTENSION pg_trgm;

CREATE TABLE IF NOT EXISTS users (
id SERIAL,
name VARCHAR(35),
username VARCHAR(25) NOT NULL UNIQUE,
Expand All @@ -14,33 +15,31 @@ CREATE TABLE users (
UNIQUE (username, email),
PRIMARY KEY (id)
);
CREATE INDEX users_name_trigram_idx ON users USING GIST (name gist_trgm_ops);
CREATE INDEX users_username_trigram_idx ON users USING GIST (username gist_trgm_ops);

DROP TABLE IF EXISTS users_sessions;
CREATE TABLE users_sessions (
CREATE TABLE IF NOT EXISTS users_sessions (
id UUID NOT NULL,
user_id INTEGER NOT NULL,
PRIMARY KEY (id, user_id),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);

DROP TABLE IF EXISTS users_follows;
CREATE TABLE users_follows (
CREATE TABLE IF NOT EXISTS users_follows (
follower INTEGER NOT NULL,
following INTEGER NOT NULL,
PRIMARY KEY (follower, following),
FOREIGN KEY (follower) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (following) REFERENCES users (id) ON DELETE CASCADE
);

DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
CREATE TABLE IF NOT EXISTS tags (
id SERIAL,
name VARCHAR,
PRIMARY KEY (id)
);

DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
CREATE TABLE IF NOT EXISTS posts (
id SERIAL,
description VARCHAR(250),
user_id INTEGER,
Expand All @@ -51,17 +50,15 @@ CREATE TABLE posts (
PRIMARY KEY (id)
);

DROP TABLE IF EXISTS post_likes;
CREATE TABLE post_likes (
CREATE TABLE IF NOT EXISTS post_likes (
post_id INTEGER,
user_id INTEGER,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, user_id)
);

DROP TABLE IF EXISTS post_tags;
CREATE TABLE post_tags (
CREATE TABLE IF NOT EXISTS post_tags (
id SERIAL,
post_id INTEGER,
tag_id INTEGER,
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ build:
sudo docker build -t friendstagram/$(SERVICE) .

run:
docker-compose -f docker-compose.yml up -d friendstagram
sudo docker-compose -f docker-compose.yml up -d friendstagram

restart:
make build
sudo docker-compose -f docker-compose.yml up --build -d friendstagram
11 changes: 11 additions & 0 deletions controllers/search_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const usersModel = require('../model/users_model')
const {Response} = require('../response-types')

module.exports.searchUsers = async ({query: {query}}, res, next) => {
try {
const results = await usersModel.search(query)
return Response.OK(res, results)
} catch (e) {
next(e)
}
}
18 changes: 9 additions & 9 deletions controllers/users_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,35 +64,35 @@ module.exports.login = async ({body}, res, next) => {
}

try {
const {id: uuid, user_id: userID} = await userModel.login(username, password)
const {id: uuid, user_id: userID, verified, profile_picture_url} = await userModel.login(username, password)
const payload = {
id: userID,
timestamp: new Date(),
uuid
}
const token = jwt.encode(payload, cfg.jwtSecret)
return Response.OK(res, token)
return Response.OK(res, {token, verified, profile_picture_url})
} catch (e) {
if (e.message === 'Require key') {
console.error('jwtSecret is missing')
return next(FSError.unknown())
}
next(FSError.unknown())
next(e)
}
}

module.exports.changeUser = async ({user, body}, res, next) => {
const {id} = user
const {old_password: oldPassword, new_password: newPassword} = body

let errors = utils.getMissingKeys(user, [{key: 'id', name: 'user ID'}])
errors = errors.concat(utils.getMissingKeys(body, [{key: 'old_password', name: 'old password'}, {key: 'new_password', name: 'new password'}]))
errors = errors.concat(utils.getMissingKeys(body, [{key: 'old_password', name: 'old password'}]))
if (!errors.isEmpty()) {
return next(FSError.missingParameters({errors}))
}

try {
await userModel.changePassword(id, oldPassword, newPassword)
return Response.OK(res, {title: 'Successfully updated user'})
await userModel.changeUser(id, body)
return Response.OK(res, 'Successfully updated user')
} catch (e) {
next(e)
}
Expand Down Expand Up @@ -148,9 +148,9 @@ module.exports.updateBackgroundPicture = async ({user: {id = null}, body: {image
}
}

module.exports.getDefaultProfilePicture = async ({user: {id = null}}, res, next) => {
module.exports.getDefaultProfilePicture = async ({query: {username = null}}, res, next) => {
try {
const [{username, datecreated}] = await userModel.findUserByID(id)
const [{datecreated}] = await userModel.findUserByUsername(username)
const today = new Date()
res.set({
'Content-Type': 'image/png',
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ app.get('/ping', (req, res) => {
app.use('/users', require('./routes/user-routes'))
app.use('/posts', require('./routes/post-routes'))
app.use('/follow', require('./routes/follow-routes'))
app.use('/search', require('./routes/search-routes'))
app.use(errorHandler)

const port = process.env.PORT || 8080
Expand Down
2 changes: 1 addition & 1 deletion model/follow_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ module.exports.getAllFollowing = userID => db.raw(
[userID]).rows()

module.exports.getAllFollowers = userID => db.raw(
'SELECT u.id, u.name, u.username, u.profile_picture_url FROM users u JOIN users_followers uf ON u.id = uf.follower WHERE u.following = $1',
'SELECT u.id, u.name, u.username, u.profile_picture_url FROM users u JOIN users_follows uf ON u.id = uf.follower WHERE uf.following = $1',
[userID]).rows()
81 changes: 45 additions & 36 deletions model/users_model.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const bcrypt = require('bcrypt')
const uuid = require('uuid')

const followModel = require('./follow_model')
const config = require('../config')
const db = require('pg-bricks').configure(config[process.env.NODE_ENV || 'development'])
const utils = require('../utils/util')
Expand All @@ -23,46 +24,28 @@ module.exports.register = async (username, unHashedPassword, email, name) => {

module.exports.findUserByUsername = async (username) => {
const userPromise = db.select([
'id',
'name',
'username',
'datecreated',
'description',
'profile_picture_url',
'profile_background_url']).from('users').where({username}).row()
'profile_background_url'
]).from('users').where({username}).row()

const postsPromise = db.raw(
'SELECT * FROM posts WHERE user_id = (SELECT id FROM users WHERE username = $1) ORDER BY id DESC;',
[username]).rows()

const followerIDsPromise = db.raw(
'SELECT uf.follower FROM users_follows uf JOIN users u ON uf.following = u.id WHERE uf.following = (SELECT id FROM users WHERE username = $1);',
[username]).rows()

const followingIDsPromise = db.raw(
'SELECT uf.following FROM users_follows uf JOIN users u ON uf.follower = u.id WHERE uf.follower = (SELECT id FROM users WHERE username = $1);',
[username]).rows()

const [followerIDs, followingIDs] = await Promise.all(
[followerIDsPromise, followingIDsPromise])

let followersPromise
if (followerIDs.length !== 0) {
const params = followerIDs.map((id, index) => `$${index + 1}`).join(', ')
followersPromise = db.raw(
`SELECT username FROM users WHERE id IN (${params})`,
followerIDs.map(({follower}) => follower)).rows()
}

let followingPromise
if (followingIDs.length !== 0) {
const params = followingIDs.map((id, index) => `$${index + 1}`).join(', ')
followingPromise = db.raw(
`SELECT username FROM users WHERE id IN (${params})`,
followingIDs.map(({following}) => following)).rows()
}
[username]
).rows()

try {
return await Promise.all([userPromise, postsPromise, followersPromise, followingPromise])
const [user, posts] = await Promise.all([userPromise, postsPromise])
const followersPromise = followModel.getAllFollowers(user.id)
const followingPromise = followModel.getAllFollowing(user.id)
delete user.id

const [followers, following] = await Promise.all([followersPromise, followingPromise])
return [user, posts, followers, following]
} catch (e) {
if (e.message === 'Expected a row, none found') { // user not found
throw FSError.userDoesNotExist({detail: 'User not found'})
Expand Down Expand Up @@ -106,19 +89,20 @@ module.exports.comparePasswordByID = async (id, password) => {
}

module.exports.login = async (username, password) => {
const possibleUser = await db.select(['username', 'password'])
const {id: userID, password: hashedPassword, verified, profile_picture_url} = await db
.select(['id', 'password', 'verified', 'profile_picture_url'])
.from('users')
.where({username})
.row()
const validPssd = await bcrypt.compare(password, possibleUser.password)
const validPassword = await bcrypt.compare(password, hashedPassword)

if (!validPssd) {
if (!validPassword) {
throw FSError.invalidPassword()
}

return db.raw(
'INSERT INTO users_sessions VALUES($1, (SELECT id FROM users WHERE username = $2)) RETURNING *;',
[uuid.v4(), username]).row()
const uuidStr = uuid.v4()
await db.raw('INSERT INTO users_sessions VALUES ($1, $2);', [uuidStr, userID]).run()
return {id: uuidStr, user_id: userID, verified, profile_picture_url}
}

module.exports.changePassword = async (id, password, newPassword) => {
Expand All @@ -133,6 +117,31 @@ module.exports.changePassword = async (id, password, newPassword) => {
return db.update('users').set('password', hashedPassword).where({id}).run()
}

module.exports.changeUser = async (id, updateObj) => {
const {old_password: oldPassword, new_password: newPassword} = updateObj
const passwordMatch = await module.exports.comparePasswordByID(id, oldPassword)
if (!passwordMatch) {
throw FSError.invalidPassword()
}

const promises = []
if (newPassword) {
promises.push(module.exports.changePassword(id, oldPassword, newPassword))
}
delete updateObj.old_password
delete updateObj.new_password

const keys = Object.keys(updateObj)
const query = `UPDATE users SET ${keys.map((key, index) => `${key} = $${index + 1}`).join(', ')} WHERE id = $${keys.length + 1};`
const params = keys.map(key => updateObj[key])
params.push(id)
promises.push(db.raw(query, params).run())

await Promise.all(promises)
}

module.exports.search = query => db.raw('SELECT username, name, email, profile_picture_url FROM users, similarity(name, $1) AS name_similarity, similarity(username, $1) AS username_similarity WHERE (name % $1 OR username % $1) AND profile_picture_url IS NOT NULL ORDER BY name_similarity DESC, username_similarity DESC', [query]).rows()

module.exports.logOff = id => db.delete('users_sessions').where({id}).run()

module.exports.logOffAllOtherSessions = (id, requestedSession) => db.raw(
Expand Down
Loading