diff --git a/server/database/mongoose.js b/server/database/mongoose.js new file mode 100644 index 0000000..ff12926 --- /dev/null +++ b/server/database/mongoose.js @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; + +export const dbName = process.env.DB_NAME || "sm_db"; +export const mongoURI = process.env.MONGODB_URI || "mongodb://127.0.0.1:27017"; + +// Connection documentation: https://mongoosejs.com/docs/connections.html +try { + mongoose.connect(mongoURI, { + useNewUrlParser: true, + useUnifiedTopology: true, + dbName: dbName, + }); +} catch (error) { + console.error(error); +} +export { mongoose }; \ No newline at end of file diff --git a/server/middleware/Database.js b/server/middleware/Database.js new file mode 100644 index 0000000..7f6a9c0 --- /dev/null +++ b/server/middleware/Database.js @@ -0,0 +1,9 @@ +import { mongoose } from "../database/mongoose.js"; + +export function needDatabase(res, req, next) { + if (mongoose.connection.readyState != 1) { + res.status(500).send("Internal server error: Database connection faulty."); + } else { + next(); + } +} \ No newline at end of file diff --git a/server/schemas/Match.js b/server/schemas/Match.js new file mode 100644 index 0000000..b18dcda --- /dev/null +++ b/server/schemas/Match.js @@ -0,0 +1,26 @@ +import mongoose from "mongoose"; +import ModelNameRegister from "./ModelNameRegister.js"; + +const Types = mongoose.Schema.Types; // Some types require defining from this object. + +const matchSchema = new mongoose.Schema({ + title: { type: String, required: true, trim: true }, + dateTime: { type: Date, required: true }, + public: { type: Boolean, required: true, default: true }, + location: { + type: [Number], + required: true, + validate: { + validator: function (v) { + return v.length === 2; + }, + message: "Invalid coordinate format (array not length of 2)" + } + }, + creator: { type: Types.ObjectId, ref: ModelNameRegister.User }, + participants: { type: [{ type: Types.ObjectId, ref: ModelNameRegister.User }], required: true, default: [] }, + difficulty: { type: Number, required: true }, + sport: { type: Types.ObjectId, ref: ModelNameRegister.Sport } +}); + +export default mongoose.model(ModelNameRegister.Match, matchSchema); \ No newline at end of file diff --git a/server/schemas/ModelNameRegister.js b/server/schemas/ModelNameRegister.js new file mode 100644 index 0000000..3c3b6e6 --- /dev/null +++ b/server/schemas/ModelNameRegister.js @@ -0,0 +1,5 @@ +export default { + Match: "match", + User: "user", + Sport: "sport" +}; \ No newline at end of file diff --git a/server/schemas/Sport.js b/server/schemas/Sport.js new file mode 100644 index 0000000..eb2e301 --- /dev/null +++ b/server/schemas/Sport.js @@ -0,0 +1,19 @@ +import mongoose from "mongoose"; +import ModelNameRegister from "./ModelNameRegister.js"; + +const sportSchema = new mongoose.Schema({ + name: { type: String, required: true, unique: true, trim: true }, + minPlayers: { type: Number, required: true, default: 1 }, + description: { type: String, required: true, trim: true } +}); + +sportSchema.pre("save", function (next) { + this.name = this.name.toLowerCase(); + next(); +}); + +sportSchema.statics.findByName = function (name) { + return this.findOne({ name: name.trim().toLowerCase() }); +}; + +export default mongoose.model(ModelNameRegister.Sport, sportSchema); \ No newline at end of file diff --git a/server/schemas/User.js b/server/schemas/User.js new file mode 100644 index 0000000..46ea16a --- /dev/null +++ b/server/schemas/User.js @@ -0,0 +1,78 @@ +import mongoose from "mongoose"; +import validator from "validator"; +import bcrypt from "bcrypt"; +import ModelNameRegister from "./ModelNameRegister.js"; + +const Types = mongoose.Schema.Types; + +const userSchema = new mongoose.Schema({ + email: { + value: { + type: String, + required: true, + minlength: 1, + trim: true, + unique: true, + validate: { + validator: validator.isEmail, + message: "String not email.", + } + }, + public: { type: Boolean, required: true, default: false } + }, + firstName: { + value: { type: String, required: true, trim: true }, + public: { type: Boolean, required: true, default: false } + }, + lastName: { + value: { type: String, required: true, trim: true }, + public: { type: Boolean, required: true, default: false } + }, + joinDate: { type: Date, default: Date.now, required: true }, + phone: { + value: { type: Number, required: false, min: 0 }, + public: { type: Boolean, required: true, default: false } + }, + password: { + type: String, + required: true, + minlength: 8 + // TODO: Custom validator for password requirements? + }, + createdMatches: { type: [{ type: Types.ObjectId, ref: ModelNameRegister.Match }], required: true, default: [] }, + participatingMatches: { + value: { type: [{ type: Types.ObjectId, ref: ModelNameRegister.Match }], required: true, default: [] }, + public: { type: Boolean, required: true, default: false } + }, + accessLevel: { type: Number, required: true, default: 0 } +}); + +userSchema.statics.credentialsExist = async function (email, password) { + let userModel = this; + let user = await userModel.findOne({ "email.value": email }); + if (!user) { + return Promise.reject(new Error("Credentials do not exist.")); + } + if (await bcrypt.compare(password, user.password)) { + return user; + } +}; + +userSchema.pre("save", function (next) { + let user = this; + if (user.isModified("password")) { // Only perform hashing if the password has changed. + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(user.password, salt, (err, hash) => { + if (err) { + throw err; // Probably not, but I'm gonna leave this here for now. + } + user.password = hash; + next(); + }); + }); + } else { + next(); + } +}); + +export default mongoose.model(ModelNameRegister.User, userSchema); \ No newline at end of file