diff --git a/.gitignore b/.gitignore index 5af23d6..db75230 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,7 @@ dist .pnp.* # ayylmao -ayylmao.js \ No newline at end of file +ayylmao.js + +# Python video server - generated TLS material (certbot state) +python_video_server/certs/ \ No newline at end of file diff --git a/README.md b/README.md index 9289469..c991adf 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,55 @@ -## Oscars Pool 2025 -Little nodejs + mongodb app that I hack together with ChatGPT to keep track of watched movies for an incoming Oscars Pool with some friends. App is not secure, using old html standard and is not the most responsive but does the job. +## Oscars Pool 2026 +Little nodejs + mongodb app that I hack together with ChatGPT and Cursor to keep track of watched movies for an incoming Oscars Pool with some friends. -Would probably need to revamp everything, but didn't want to spend more time on it. +![Oscars Pool 2026](https://github.com/quoije/oscars-pool/blob/2026-video.backend/img/preview.png) -![Oscars Pool 2025](https://github.com/quoije/oscar-pool/blob/prod/img/preview.png) +## Features +- User accounts: register, login, JWT auth +- Simple registration verification via `DOG_NAMES` +- Mark nominees as watched (per user), with watched date +- Browse movies by Oscar year + API for available years +- User stats: watched count/ratio and watched titles +- Movie details pulled from OMDb (title/plot/rating/poster) via IMDb ID +- Admin movie management: add, edit (optionally refresh from OMDb), delete +- Built-in player page with multiple source options (VOD link, direct video file, embed) +- Resume playback: per-user playback progress saving (time/duration) +- Global settings: active Oscar year, “completion” modal content for checklist +- Password flows: admin temp password reset + forced change on next login +- App version display (footer) managed from Admin panel (no GitHub dependency) ## Setup -Config those in your .env file: +Create a `.env` file at the project root (same folder as `package.json`). -**DOG_NAMES=**`woof, WOOF, Woof`     (for "verification")
-**GITHUB_OWNER=**`quoije`     (for version history)
-**GITHUB_REPO=**`oscars-pool`     (for version history)
-**JWT_SECRET=**`RANDOM STRING`     (for authentication token)
-**MONGO_URI=**`mongodb+srv://USER:PASS@cluster0.XXXX.mongodb.net/?retryWrites=true&w=majority&appName=Cluster69`
-**OMDB_API=**`1234567`     (for movie info) +Minimal steps: -run with **node .\index.js** \ No newline at end of file +```bash +npm install +node index.js +``` + +Add this to your `.env` (**required**: `MONGO_URI`, `JWT_SECRET`): + +```bash +# required +MONGO_URI=mongodb://127.0.0.1:27017/oscars-pool # MongoDB connection string +JWT_SECRET=replace-with-a-random-string # JWT signing secret (auth) + +# optional +DOG_NAMES=woof,WOOF,Woof # simple registration "verification" +OMDB_API=your_omdb_key # OMDb API key (movie info) +``` + +Generate a good `JWT_SECRET`: + +```bash +openssl rand -hex 32 +``` + +## Database (MongoDB) +This app uses **MongoDB** via Mongoose. Provide a connection string in `MONGO_URI`. + +- Use **MongoDB Atlas** (recommended) or a local MongoDB instance. +- `MONGO_URI` examples: + - Atlas: `mongodb+srv://USER:PASS@cluster0.XXXX.mongodb.net/?retryWrites=true&w=majority` + - Local: `mongodb://127.0.0.1:27017/` +- Collections are created/managed automatically from the models in `models/` (e.g. `User`, `Movie`, `Setting`, `PlaybackProgress`). diff --git a/img/preview.png b/img/preview.png index b8a7ea3..40666e8 100644 Binary files a/img/preview.png and b/img/preview.png differ diff --git a/index.js b/index.js index f0a5f43..877665a 100644 --- a/index.js +++ b/index.js @@ -2,12 +2,14 @@ const express = require("express"); const mongoose = require("mongoose"); const dotenv = require("dotenv"); const cors = require("cors"); +const compression = require("compression"); dotenv.config(); const app = express(); // Middleware app.use(cors()); +app.use(compression()); app.use(express.json()); // Connect to MongoDB @@ -20,11 +22,17 @@ mongoose const userRoutes = require("./routes/users"); const movieRoutes = require("./routes/movies"); const versionRoutes = require("./routes/version"); +const settingsRoutes = require("./routes/settings"); +const adminDbRoutes = require("./routes/adminDb"); +const videoRoutes = require("./routes/video"); app.use(express.static('public')); app.use("/api/users", userRoutes); app.use("/api/movies", movieRoutes); app.use("/api/version", versionRoutes); +app.use("/api/settings", settingsRoutes); +app.use("/api/admin", adminDbRoutes); +app.use("/api/video", videoRoutes); // Start Server const PORT = process.env.PORT || 5000; diff --git a/models/Movie.js b/models/Movie.js index 010246d..5083547 100644 --- a/models/Movie.js +++ b/models/Movie.js @@ -6,9 +6,28 @@ const movieSchema = new mongoose.Schema({ description: { type: String, required: false }, rating: { type: String, required: false }, poster: { type: String, required: false }, + // Oscar pool year / edition (used to segment movies + stats) + year: { type: Number, required: false, index: true }, category: { type: String, required: true }, - vod_link: { type: String, required: true }, + // Legacy "one field" player source. + // Kept for backwards-compat. New UI prefers video_src / embed_src. + vod_link: { type: String, required: false }, + + // Player configuration (optional; used by player.html) + player_mode: { type: String, required: false, enum: ["auto", "video", "embed"], default: "auto" }, + video_src: { type: String, required: false }, // direct video file URL or HLS (.m3u8) + embed_src: { type: String, required: false }, // URL or iframe code + // Server-hosted file (relative path under VIDEO_FILES_DIR, default public/video) + // If set, the backend can stream it via /api/video/:movieId (Range/seek supported). + video_file: { type: String, required: false }, watchedBy: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }] -}); +}, { timestamps: true }); + +// Common read patterns: +// - list movies for a year, sorted by title +// - check "last update" per year (sort by updatedAt) +movieSchema.index({ year: 1, title: 1 }); +movieSchema.index({ year: 1, updatedAt: -1 }); +movieSchema.index({ year: 1, imdb_id: 1 }); module.exports = mongoose.model("Movie", movieSchema); diff --git a/models/PlaybackProgress.js b/models/PlaybackProgress.js new file mode 100644 index 0000000..f81a9ff --- /dev/null +++ b/models/PlaybackProgress.js @@ -0,0 +1,20 @@ +const mongoose = require("mongoose"); + +const playbackProgressSchema = new mongoose.Schema( + { + userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, index: true }, + movieId: { type: mongoose.Schema.Types.ObjectId, ref: "Movie", required: true, index: true }, + // seconds + time: { type: Number, default: 0 }, + // seconds (optional, best-effort from the client) + duration: { type: Number, default: null }, + updatedAt: { type: Date, default: Date.now }, + }, + { versionKey: false } +); + +// One progress row per user + movie +playbackProgressSchema.index({ userId: 1, movieId: 1 }, { unique: true }); + +module.exports = mongoose.model("PlaybackProgress", playbackProgressSchema); + diff --git a/models/Setting.js b/models/Setting.js new file mode 100644 index 0000000..f5eea63 --- /dev/null +++ b/models/Setting.js @@ -0,0 +1,14 @@ +const mongoose = require("mongoose"); + +// Simple key/value store for global site settings. +// We currently use it for the "active Oscar year" that drives filtering. +const settingSchema = new mongoose.Schema( + { + key: { type: String, required: true, unique: true, index: true }, + value: { type: mongoose.Schema.Types.Mixed, required: true }, + }, + { timestamps: true } +); + +module.exports = mongoose.model("Setting", settingSchema); + diff --git a/models/User.js b/models/User.js index 6ad82bf..14751e3 100644 --- a/models/User.js +++ b/models/User.js @@ -5,6 +5,10 @@ const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true }, role: { type: Number, default: 0 }, + // If true, user must change password on next login (e.g. after admin reset) + mustChangePassword: { type: Boolean, default: false }, + tempPasswordIssuedAt: { type: Date, default: null }, + tempPasswordExpiresAt: { type: Date, default: null }, watchedMovies: [{ imdb_id: { type: String, required: false }, watchedDate: { type: Date, default: Date.now } // Store the watched date diff --git a/package-lock.json b/package-lock.json index 6bf8eb7..b5897d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,14 @@ "axios": "^1.7.9", "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", + "compression": "^1.8.1", "cors": "^2.8.5", "dotenv": "^16.4.7", - "express": "^4.21.2", + "express": "^4.22.1", "jsonwebtoken": "^9.0.2", "moment-timezone": "^0.5.47", - "mongoose": "^8.9.5" + "mongoose": "^8.9.5", + "multer": "^2.0.2" }, "devDependencies": { "nodemon": "^3.1.9" @@ -74,6 +76,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -87,13 +95,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -148,9 +156,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -186,6 +194,23 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -261,6 +286,45 @@ "node": ">= 0.8" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -268,6 +332,21 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -434,6 +513,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -450,39 +544,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -495,6 +589,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -547,13 +656,15 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -686,6 +797,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -823,23 +949,23 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -982,6 +1108,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -1137,6 +1284,24 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1243,6 +1408,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1345,6 +1519,20 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1565,6 +1753,23 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1635,6 +1840,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -1651,6 +1862,12 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1690,6 +1907,15 @@ "engines": { "node": ">=18" } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } } } } diff --git a/package.json b/package.json index 51dc847..216c776 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "axios": "^1.7.9", "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", + "compression": "^1.8.1", "cors": "^2.8.5", "dotenv": "^16.4.7", - "express": "^4.21.2", + "express": "^4.22.1", "jsonwebtoken": "^9.0.2", "moment-timezone": "^0.5.47", - "mongoose": "^8.9.5" + "mongoose": "^8.9.5", + "multer": "^2.0.2" }, "devDependencies": { "nodemon": "^3.1.9" diff --git a/public/change-password.html b/public/change-password.html new file mode 100644 index 0000000..f497085 --- /dev/null +++ b/public/change-password.html @@ -0,0 +1,45 @@ + + + + + + Pool Oscars - Changer le mot de passe + + + + + +
+
+
+ +
+ + + + + +
+
+ + +
Minimum 8 caractères.
+
+
+ + +
+ +
+ + +
+
+ + + + + + diff --git a/public/checklist.html b/public/checklist.html index 7952836..2233d28 100644 --- a/public/checklist.html +++ b/public/checklist.html @@ -3,9 +3,11 @@ - Pool Oscars 2025 - Checklist + Pool Oscars - Checklist + + - + + + +
+
+ Chargement… + +
+