diff --git a/.env.example b/.env.example deleted file mode 100644 index 848ad61..0000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://hostname:5432/database-name -JWT_SECRET=super-secret-jwt-key -FRONTEND_URL=http://localhost:3000 diff --git a/api/index.js b/api/index.js index f08162e..25fea5f 100644 --- a/api/index.js +++ b/api/index.js @@ -1,7 +1,8 @@ const express = require("express"); const router = express.Router(); const testDbRouter = require("./test-db"); +const usersRouter = require("./users") router.use("/test-db", testDbRouter); - +router.use("/users", usersRouter); module.exports = router; diff --git a/api/users.js b/api/users.js new file mode 100644 index 0000000..abe1d25 --- /dev/null +++ b/api/users.js @@ -0,0 +1,28 @@ +const express = require("express") +const router = express.Router(); +const { User } = require("../database"); + +router.get("/", async (req, res) => { + try { + const users = await User.findAll({ + attributes: { exclude: ["password_hash"] } + }); + res.json(users); + } catch (err) { + res.status(500).json({ error: "Failed to fetch users" }); + } +}); + +router.get("/:id", async (req, res) => { + try { + const user = await User.findByPk(req.params.id, { + attributes: { exclude: ["password_hash"] } + }); + + res.json(user); + } catch (err) { + res.status(500).json({ error: "Failed to fetch user" }) + } +}); + +module.exports = router; \ No newline at end of file diff --git a/database/db.js b/database/db.js index b251a9d..3b580f1 100644 --- a/database/db.js +++ b/database/db.js @@ -3,7 +3,7 @@ const { Sequelize } = require("sequelize"); const pg = require("pg"); // Feel free to rename the database to whatever you want! -const dbName = "capstone-1"; +const dbName = "capstone-2"; const db = new Sequelize( process.env.DATABASE_URL || `postgres://localhost:5432/${dbName}`, diff --git a/database/echo_recipients.js b/database/echo_recipients.js new file mode 100644 index 0000000..2e3223e --- /dev/null +++ b/database/echo_recipients.js @@ -0,0 +1,29 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Echo_recipients = db.define("echo_recipients", { + echo_id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + references: { + model: 'echoes', // target table name + key: 'id' + }, + }, + + recipient_id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + references: { + model: 'users', // target table name + key: 'id' + }, + } +}, { + tableName: 'echo_recipients', + timestamps: false +}); + +module.exports = Echo_recipients; diff --git a/database/echo_tags.js b/database/echo_tags.js new file mode 100644 index 0000000..f018005 --- /dev/null +++ b/database/echo_tags.js @@ -0,0 +1,29 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Echo_tags = db.define('echo_tags', { + echo_id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: "echoes", // target table + key: "id" + } + }, + + tag_id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: "tags", + key: "id" + } + } +}, { + tableName: 'echo_tags', + timestamps: false +}); + +module.exports = Echo_tags; diff --git a/database/echo_visibility.js b/database/echo_visibility.js new file mode 100644 index 0000000..a465273 --- /dev/null +++ b/database/echo_visibility.js @@ -0,0 +1,26 @@ +const { DataTypes} = require("sequelize"); +const db = require("./db"); + +const Echo_visibility = db.define("echo_visibility", { + echo_id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + }, + + scope: { + type: DataTypes.ENUM("self", "friends", "public", "custom"), + allowNull: false, + + }, + + note: { + type: DataTypes.TEXT, + allowNull: true + } +}, { + tableName: 'echo_visibility', + timestamps: false, +}); + +module.exports = Echo_visibility; diff --git a/database/echoes.js b/database/echoes.js new file mode 100644 index 0000000..bb471e1 --- /dev/null +++ b/database/echoes.js @@ -0,0 +1,62 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Echoes = db.define("echoes", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + + sender_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', // target table + key: 'id' + } + }, + + type: { + type: DataTypes.ENUM("self", "friend", "public"), + allowNull: false, + }, + + text: { + type: DataTypes.STRING, + allowNull: true, + }, + + unlock_datetime: { + type: DataTypes.DATE, + allowNull: false + }, + + is_unlocked: { + type: DataTypes.VIRTUAL, + get() { + const now = new Date(); + return now >= this.unlock_datetime; + } + }, + + is_archived: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false, + }, + + show_sender_name: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false, + } + +}, { + tableName: 'echoes', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', +}); + +module.exports = Echoes; diff --git a/database/friends.js b/database/friends.js new file mode 100644 index 0000000..6ce2209 --- /dev/null +++ b/database/friends.js @@ -0,0 +1,48 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Friends = db.define("friends", { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + + friend_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + + status: { + type: DataTypes.ENUM('pending', 'accepted', 'blocked'), + allowNull: false, + defaultValue: 'pending' + } +}, { + tableName: 'friends', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + indexes: [ + { + unique: true, + fields: ['user_id', 'friend_id'], + name: 'unique_user_friend_pair' + } + ] +}); + +module.exports = Friends; \ No newline at end of file diff --git a/database/index.js b/database/index.js index e498df6..6fbf6c4 100644 --- a/database/index.js +++ b/database/index.js @@ -1,5 +1,11 @@ const db = require("./db"); const User = require("./user"); +const Echoes = require('./echoes'); +const Echo_visibility = require('./echo_visibility'); +const Echo_recipients = require('./echo_recipients'); +const Echo_tags = require('./echo_tags'); + + module.exports = { db, diff --git a/database/media.js b/database/media.js new file mode 100644 index 0000000..8d4afef --- /dev/null +++ b/database/media.js @@ -0,0 +1,74 @@ +const { DataTypes } = require("sequelize"); +const db = require("/.db"); + +const Media = db.define("media", { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + + echo_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'echoes', + key: 'id' + } + }, + + reply_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'replies', + key: 'id' + } + }, + + type: { + type: DataTypes.ENUM('image', 'audio', 'video'), + allowNull: false, + }, + + url: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isUrl: true + } + }, + + file_size: { + type: DataTypes.INTEGER, + allowNull: false + }, + + duration_seconds: { + // only applies to audio or video + type: DataTypes.INTEGER, + allowNull: true + }, + + thumbnail_url: { + // optional thumbnail + type: DataTypes.STRING, + allowNull: true + } +}, { + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + validate: { + eitherEchoOrReply() { + if (!this.echo_id && !this.reply_id) { + throw new Error('Media must belong to either an echo or reply.'); + } + if (this.echo_id && this.reply_id) { + throw new Error('Media cannot belong to both an echo and a reply.'); + } + } + } +}); + +module.exports = Media; diff --git a/database/reactions.js b/database/reactions.js new file mode 100644 index 0000000..3d8d87d --- /dev/null +++ b/database/reactions.js @@ -0,0 +1,44 @@ +const { DataTypes } = require('sequelize'); +const db = require('./db'); + +const Reactions = db.define('reactions', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + + echo_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'echoes', + key: 'id' + } + }, + + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + + type: { + type: DataTypes.ENUM('sad', 'funny', 'happy'), + allowNull: false + } +}, { + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + indexes: [{ + unique: true, + fields: ['user_id', 'echo_id'], + name: 'unique_user_echo_reaction_pair' + }] +}); + +module.exports = Reactions; diff --git a/database/replies.js b/database/replies.js new file mode 100644 index 0000000..9f8f7d2 --- /dev/null +++ b/database/replies.js @@ -0,0 +1,44 @@ +const { DataTypes } = require('sequelize'); +const db = require('./db'); + +const Replies = db.define('replies', { + id: { + primaryKey: true, + autoIncrement: true, + type: DataTypes.INTEGER + }, + + echo_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'echoes', + key: 'id' + } + }, + + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + + parent_reply_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'replies', + key: 'id' + } + } +}, { + tableName: 'replies', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at' +}); + +module.exports = Replies; \ No newline at end of file diff --git a/database/reports.js b/database/reports.js new file mode 100644 index 0000000..d83737f --- /dev/null +++ b/database/reports.js @@ -0,0 +1,56 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Reports = db.define("reports", { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + + echo_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'echoes', + key: 'id' + } + }, + + reporter_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + + type: { + type: DataTypes.ENUM('spam', 'harrassment', 'hate_speech', 'explicit', 'other'), + allowNull: false, + }, + + reasons: { + // option message for report + type: DataTypes.TEXT, + allowNull: true + }, + + status: { + type: DataTypes.ENUM('pending', 'reviewed', 'dismissed'), + allowNull: false, + defaultValue: 'pending' + }, + + reviewed_at: { + type: DataTypes.DATE, + allowNull: true + } +}, { + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', +}); + +module.exports = Reports; \ No newline at end of file diff --git a/database/seed.js b/database/seed.js index e58b595..a61cbcd 100644 --- a/database/seed.js +++ b/database/seed.js @@ -7,9 +7,10 @@ const seed = async () => { await db.sync({ force: true }); // Drop and recreate tables const users = await User.bulkCreate([ - { username: "admin", passwordHash: User.hashPassword("admin123") }, - { username: "user1", passwordHash: User.hashPassword("user111") }, - { username: "user2", passwordHash: User.hashPassword("user222") }, + { username: "jeramy", password_hash: User.hashPassword("123456"), email: "jeramy@gmail.com" }, + { username: "aiyanna", password_hash: User.hashPassword("456"), email: "aiyanna@gmail.com" }, + { username: "emmanuel", password_hash: User.hashPassword("789"), email: "emmanuel@gmail.com" }, + { username: "olivia", password_hash: User.hashPassword("101112"), email: "olivia@gmail.com" }, ]); console.log(`👤 Created ${users.length} users`); diff --git a/database/tags.js b/database/tags.js new file mode 100644 index 0000000..93e9352 --- /dev/null +++ b/database/tags.js @@ -0,0 +1,21 @@ +const { DataTypes } = require("sequelize"); +const db = require("./db"); + +const Tags = db.define("tags", { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + autoIncrement: true, // auto generated tags IDs + }, + + name: { + type: DataTypes.TEXT, + allowNull: false, + } +}, { + tableName: 'tags', + timestamps: true +}); + +module.exports = Tags; diff --git a/database/user.js b/database/user.js index 755c757..2bf92ad 100644 --- a/database/user.js +++ b/database/user.js @@ -3,39 +3,61 @@ const db = require("./db"); const bcrypt = require("bcrypt"); const User = db.define("user", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + username: { type: DataTypes.STRING, allowNull: false, - unique: true, - validate: { - len: [3, 20], - }, + unique: true + }, + + bio: { + type: DataTypes.TEXT, + allowNull: true }, + + avatar_url: { + type: DataTypes.ENUM("image", "gif"), + allowNull: true, + }, + email: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, unique: true, validate: { isEmail: true, }, }, + auth0Id: { type: DataTypes.STRING, allowNull: true, unique: true, }, - passwordHash: { + + password_hash: { type: DataTypes.STRING, allowNull: true, - }, + comment: "Securely hashed password", + }, +},{ + tableName: 'users', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', }); // Instance method to check password User.prototype.checkPassword = function (password) { - if (!this.passwordHash) { + if (!this.password_hash) { return false; // Auth0 users don't have passwords } - return bcrypt.compareSync(password, this.passwordHash); + return bcrypt.compareSync(password, this.password_hash); }; // Class method to hash password diff --git a/package-lock.json b/package-lock.json index af0cf82..1b30289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "license": "ISC", "dependencies": {