diff --git a/database/db.js b/database/db.js index b251a9d..bba1d5f 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_1"; const db = new Sequelize( process.env.DATABASE_URL || `postgres://localhost:5432/${dbName}`, diff --git a/database/index.js b/database/index.js index e498df6..20775f3 100644 --- a/database/index.js +++ b/database/index.js @@ -1,7 +1,77 @@ const db = require("./db"); -const User = require("./user"); +const User = require("./models/user"); +const Poll = require("./models/poll"); +const PollOption = require("./models/pollOption"); +const VotingRank = require("./models/votingRank"); +const Vote = require("./models/vote"); + +//One to many - user has many polls +User.hasMany(Poll, { + foreignKey: 'userId', + // onDelete: 'CASCADE' deletes poll is user is deleted +}); + + +// many to one - Each Poll belongs to one user +Poll.belongsTo(User, { + foreignKey: 'userId', +}); + + +// One to many - one Poll has many options +Poll.hasMany(PollOption, { + foreignKey: 'pollId', + onDelete: "CASCADE", // delete poll_options if poll is deleted +}); + + +// many to one - Each pollOption belongs to one Poll +PollOption.belongsTo(Poll, { + foreignKey: "pollId" +}); + + +// one to many- each user can submit many votes +User.hasMany(Vote, { + foreignKey: "userId", +}) + + +// one to one- Each vote(ballot) belongs to a user +Vote.belongsTo(User, { + foreignKey: "userId", +}) + + +// many to one - Each vote(ballot) belongs to a poll +Vote.belongsTo(Poll, { + foreignKey: "pollId" +}) + + +// one to many- each vote(ballot) can have many ranked options +Vote.hasMany(VotingRank, { + foreignKey: "voteId", +}) + + +// many to one - each rank entry belongs to one vote(ballot) +VotingRank.belongsTo(Vote, { + foreignKey: "voteId", +}) + + +// many to one- Each voteRank belongs to one Polloption +VotingRank.belongsTo(PollOption, { + foreignKey: 'pollOptionId', +}) + module.exports = { db, User, + Poll, + PollOption, + Vote, + VotingRank, }; diff --git a/database/models/poll.js b/database/models/poll.js new file mode 100644 index 0000000..5031a1f --- /dev/null +++ b/database/models/poll.js @@ -0,0 +1,45 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +// define the Poll model + +const Poll = db.define("poll", { + title: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + // allowNull: true, + }, + participants: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, + status: { + type: DataTypes.ENUM("draft", "published", "ended"), + allowNull: false, + }, + deadline: { + type: DataTypes.DATE, + allowNull: false, + }, + authRequired: { + type: DataTypes.BOOLEAN, // allow only user votes if true + default: false, + }, + isDisabled: { + type: DataTypes.BOOLEAN, // if true poll is disabled by admin + default: false, + }, + restricted: { + type: DataTypes.BOOLEAN, // only specic users can parcipate if true + default: false, + }, +}, + { + timestamps: true, + } +); + +module.exports = Poll; \ No newline at end of file diff --git a/database/models/pollOption.js b/database/models/pollOption.js new file mode 100644 index 0000000..0e56250 --- /dev/null +++ b/database/models/pollOption.js @@ -0,0 +1,26 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +// Table Poll_Option { +// id PK +// option_text string +// position integer ("?") +// poll_id FK +// created_at timestamp +// } +const pollOption = db.define("pollOption", { + optionText: { + type: DataTypes.STRING, + allowNull: false, + }, + position: { + type: DataTypes.INTEGER, + allowNull: true, + }, +}, + { + timestamps: true, + } +) + +module.exports = pollOption; \ No newline at end of file diff --git a/database/user.js b/database/models/user.js similarity index 53% rename from database/user.js rename to database/models/user.js index 755c757..6e82a40 100644 --- a/database/user.js +++ b/database/models/user.js @@ -1,22 +1,46 @@ const { DataTypes } = require("sequelize"); -const db = require("./db"); +const db = require("../db"); const bcrypt = require("bcrypt"); const User = db.define("user", { - username: { + userName: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, unique: true, validate: { - len: [3, 20], + len: [1, 50] + } + }, + firstName: { + type: DataTypes.STRING, + // allowNull: false, + validate: { + notEmpty: true, + len: [1, 50], + }, + }, + lastName: { + type: DataTypes.STRING, + // allowNull: false, + validate: { + notEmpty: true, + len: [1, 50], }, }, email: { type: DataTypes.STRING, - allowNull: true, + // allowNull: false, unique: true, validate: { isEmail: true, + notEmpty: true, + }, + }, + passwordHash: { + type: DataTypes.STRING, + allowNull: true, + validate: { + notEmpty: true, }, }, auth0Id: { @@ -24,11 +48,29 @@ const User = db.define("user", { allowNull: true, unique: true, }, - passwordHash: { + img: { type: DataTypes.STRING, allowNull: true, + validate: { + isUrl: true, + }, + }, + isAdmin: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, }, -}); + isDisable: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, +}, + { + timestamps: true, + createdAt: 'created_at', + } +); // Instance method to check password User.prototype.checkPassword = function (password) { diff --git a/database/models/vote.js b/database/models/vote.js new file mode 100644 index 0000000..e078ef9 --- /dev/null +++ b/database/models/vote.js @@ -0,0 +1,31 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +// define the Vote model + +const Vote = db.define("vote", { + submitted: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + voterToken: { + type: DataTypes.STRING, + allowNull: true, + }, + ipAddress: { + type: DataTypes.STRING, + allowNull: true, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + pollId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + timestamps: true, +}); + +module.exports = Vote; \ No newline at end of file diff --git a/database/models/votingRank.js b/database/models/votingRank.js new file mode 100644 index 0000000..d5a03c2 --- /dev/null +++ b/database/models/votingRank.js @@ -0,0 +1,21 @@ +const { DataTypes } = require("sequelize"); +const db = require("../db"); + +// define the votingRank model + +const VotingRank = db.define("votingRank", { + voteId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + pollOptionId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + rank: { + type: DataTypes.INTEGER, + allowNull: false, + } +}) + +module.exports = VotingRank; \ No newline at end of file diff --git a/database/seed.js b/database/seed.js index e58b595..661f859 100644 --- a/database/seed.js +++ b/database/seed.js @@ -1,5 +1,6 @@ +const { Pool } = require("pg"); const db = require("./db"); -const { User } = require("./index"); +const { User, Poll, PollOption, Vote, VotingRank } = require("./index"); const seed = async () => { try { @@ -7,13 +8,259 @@ 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: "admin", + passwordHash: User.hashPassword("admin123"), + }, + { + userName: "user1", + passwordHash: User.hashPassword("user111") + }, + { + userName: "user2", + passwordHash: User.hashPassword("user222") + }, ]); - console.log(`๐Ÿ‘ค Created ${users.length} users`); + // deadline: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), + + const pollData = [ + { + key: "anime", + title: "Best Anime?", + description: "Rank your favorite animes!", + participants: 0, + status: "published", + userKey: "user1", + + }, + { + key: "movie", + title: "Best Movie?", + description: "Rank your favorite movies!", + participants: 0, + status: "published", + userKey: "user2", + }, + { + key: "bbq", + title: "Best BBQ Item?", + description: "Rank your favorite BBQ food!", + status: "published", + userKey: "user1" + }, + { + key: "authRequired", + title: "authRequired true", + description: "?", + participants: 0, + status: "published", + authRequired: true, + userKey: "user2" + + }, + { + key: "restricited", + title: "restricted true", + description: "Rank your favorite anime of all time!", + participants: 0, + status: "published", + restricted: true, + userKey: "user1" + + }, + ]; + + const createdPolls = {}; + const deadline = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000); // 3days + + const userMap = { + admin: users[0], + user1: users[1], + user2: users[2], + }; + + for (const poll of pollData) { + const created = await Poll.create({ + ...poll, + deadline, + userId: userMap[poll.userKey].id, + }) + createdPolls[poll.key] = created; + // console.log(createdPolls.anime.id) + }; + + + + const PollOptions = await PollOption.bulkCreate([ + { + optionText: "Demon Slayer", + position: 1, + pollId: createdPolls.anime.id, + }, + { + optionText: "One Piece", + position: 2, + pollId: createdPolls.anime.id, + }, + { + optionText: "AOT", + position: 3, + pollId: createdPolls.anime.id, + }, + { + optionText: "Naruto", + position: 4, + pollId: createdPolls.anime.id, + }, + { + optionText: "Devil May Cry", + position: 5, + pollId: createdPolls.anime.id, + }, + { + optionText: "Castlevania", + position: 6, + pollId: createdPolls.anime.id, + }, + { + optionText: "Die Hard", + pollId: createdPolls.movie.id + }, + { + optionText: "Die Hard 2", + pollId: createdPolls.movie.id, + }, + { + optionText: "Twilight", + pollId: createdPolls.movie.id, + }, + { + optionText: "Spiderverse", + pollId: createdPolls.movie.id, + }, + { + optionText: "Pork Ribs", + pollId: createdPolls.bbq.id, + }, + { + optionText: "Hot Dog", + pollId: createdPolls.bbq.id, + }, + { + optionText: "Cheeseburger", + pollId: createdPolls.bbq.id, + }, + { + optionText: "Suasage", + pollId: createdPolls.bbq.id, + }, + { + optionText: "a", + pollId: createdPolls.authRequired.id, + }, + { + optionText: "b", + pollId: createdPolls.authRequired.id, + }, + { + optionText: "c", + pollId: createdPolls.authRequired.id, + }, + { + optionText: "d", + pollId: createdPolls.authRequired.id, + }, + { + optionText: "1", + pollId: createdPolls.restricited.id, + }, + { + optionText: "2", + pollId: createdPolls.restricited.id, + }, + { + optionText: "3", + pollId: createdPolls.restricited.id, + + }, + { + optionText: "4", + pollId: createdPolls.restricited.id, + }, + + ]); + + + // vote ---> envelope + const votes = await Vote.bulkCreate([ + { + userId: users[1].id, + pollId: createdPolls.anime.id + }, + { + userId: users[2].id, + pollId: createdPolls.movie.id + }, + { + userId: users[1].id, + pollId: createdPolls.bbq.id + }, + { + userId: users[2].id, + pollId: createdPolls.authRequired.id + }, + { + userId: users[1].id, + pollId: createdPolls.restricited.id + }, + ]) + + + const optionMap = {}; + PollOptions.forEach((option) => { + optionMap[option.optionText] = option; + }); + + const ranks = await VotingRank.bulkCreate([ + { + voteId: votes[0].id, + pollOptionId: optionMap["Demon Slayer"].id, + rank: 1, + }, + { + voteId: votes[0].id, + pollOptionId: optionMap["One Piece"].id, + rank: 3, + }, + { + voteId: votes[0].id, + pollOptionId: optionMap["AOT"].id, + rank: 4, + }, + { + voteId: votes[0].id, + pollOptionId: optionMap["Devil May Cry"].id, + rank: 6 + }, + { + voteId: votes[0].id, + pollOptionId: optionMap["Castlevania"].id, + rank: 5 + }, + { + voteId: votes[0].id, + pollOptionId: optionMap["Naruto"].id, + rank: 2 + }, + ]); + + + + console.log(`๐Ÿ‘ค Created ${users.length} users`); + console.log(`Created ${Object.keys(createdPolls).length} polls`); + console.log(`๐Ÿงพ Created ${PollOptions.length} poll options`); // Create more seed data here once you've created your models // Seed files are a great way to test your database schema! 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": {