├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── api
├── add-question.js
├── check-answer.js
├── clear-cache.js
├── get-config.js
├── get-leaderboard.js
├── get-logs.js
├── get-player-details.js
├── get-player-rank.js
├── get-question.js
├── get-stats.js
├── update-config.js
├── update-player-level.js
├── update-player-type.js
└── update-question.js
├── index.html
├── models
├── config.js
├── log.js
├── player.js
└── question.js
├── mongo.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── routers
├── api.js
└── auth.js
├── src
├── cache.js
├── client.js
├── components
│ ├── AdminForm.svelte
│ ├── Chevron.svelte
│ ├── Countdown.svelte
│ ├── Navigation.svelte
│ └── play
│ │ ├── Footer.svelte
│ │ ├── GameArea.svelte
│ │ ├── Navigation.svelte
│ │ ├── QuestionAnswer.svelte
│ │ └── SidePanel.svelte
├── constants.js
├── routes
│ ├── _error.svelte
│ ├── _layout.svelte
│ ├── admin.svelte
│ ├── index.svelte
│ ├── johnwickr.html
│ ├── leaderboard.svelte
│ ├── login.svelte
│ ├── logs.svelte
│ ├── register.svelte
│ ├── stats.svelte
│ ├── thatsonesmallstepformanonegiantleapformankind.html
│ └── thetis.svelte
├── server.js
├── template.html
└── utils.js
├── static
├── Illiad logo.png
├── loading.svg
├── logo.svg
├── meta
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── mstile-150x150.png
│ ├── safari-pinned-tab.svg
│ ├── site-card-old.png
│ └── site-card.png
├── paradoxf.png
└── robots.txt
├── styles
├── admin-forms.scss
├── auth-forms.scss
├── common.scss
├── global.scss
└── theme.scss
└── svelte.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | __sapper__
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2020: true,
5 | node: true,
6 | serviceworker: true,
7 | },
8 | extends: ["standard"],
9 | parserOptions: {
10 | ecmaVersion: 11,
11 | sourceType: "module",
12 | },
13 | plugins: ["svelte3"],
14 | overrides: [
15 | {
16 | files: ["*.svelte"],
17 | processor: "svelte3/svelte3",
18 | rules: {
19 | "import/first": 0,
20 | "import/no-duplicates": 0,
21 | "import/no-mutable-exports": 0,
22 | "no-return-assign": 0,
23 | "no-multiple-empty-lines": [
24 | "error",
25 | {
26 | max: 1,
27 | maxBOF: 2,
28 | maxEOF: 0,
29 | },
30 | ],
31 | },
32 | },
33 | ],
34 | rules: {},
35 | };
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /__sapper__/
6 | .env
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020, IEEE IIIT Delhi and authors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Slash
2 |
3 | > Platform for the Slash cryptic hunt
4 |
5 | ## License
6 |
7 | [MIT](LICENSE)
8 |
--------------------------------------------------------------------------------
/api/add-question.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import { log, hash } from '../src/utils'
3 | import Question from '../models/question'
4 |
5 | export default async (req, res) => {
6 | if (!req.user || !req.user.admin) {
7 | return res.json({
8 | success: false,
9 | message: constants.ERR_NOAUTH
10 | })
11 | }
12 |
13 | const { level, question, img, answer } = req.body
14 | const existingQuestion = await Question.findOne({ level })
15 |
16 | if (existingQuestion) {
17 | return res.json({
18 | success: false,
19 | message: constants.ERR_LEVEL_EXISTS
20 | })
21 | }
22 |
23 | const hashedAnswer = hash(answer.replace(/[\s]+/g, '').toLowerCase())
24 | await Question.create({
25 | level,
26 | question,
27 | img,
28 | answer: hashedAnswer
29 | })
30 |
31 | log('ADMIN', `[${req.user.username}] Question added`, `Level ${level}`)
32 |
33 | return res.json({
34 | success: true,
35 | message: constants.GENERIC_SUCC
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/api/check-answer.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import { clearCache } from '../src/cache'
3 | import { log, hash } from '../src/utils'
4 | import Question from '../models/question'
5 | import Player from '../models/player'
6 | import Config from '../models/config'
7 |
8 | export default async (req, res) => {
9 | if (!req.user) {
10 | return res.status(403).json({
11 | success: false,
12 | message: constants.ERR_NOAUTH
13 | })
14 | }
15 |
16 | // check whether game has ended
17 | const config = await Config.findOne()
18 | if (config.ended) {
19 | return res.json({
20 | success: false,
21 | reload: true,
22 | message: constants.HUNT_END
23 | })
24 | }
25 |
26 | const { username } = req.user
27 | const { answer } = req.body
28 | const invalid = !constants.ANSWER_REGEX.test(answer)
29 |
30 | const player = await Player.findOne({ username })
31 | const { level } = player
32 |
33 | if (!player) {
34 | return res.json({
35 | success: false,
36 | message: constants.ERR_NO_PLAYER
37 | })
38 | }
39 |
40 | if (player.disqualified) {
41 | return res.json({
42 | success: false,
43 | reload: true,
44 | message: constants.ERR_PLAYER_DQ
45 | })
46 | }
47 |
48 | log('ANSWER', 'Attempt', `${username} @ L${level}: ${answer}`)
49 |
50 | if (invalid) {
51 | return res.json({
52 | success: false,
53 | message: constants.ERR_INVALID_ANS
54 | })
55 | }
56 |
57 | // // Exceptional level
58 | // if (level === 4) {
59 | // let playerName
60 |
61 | // const leaderboard = await Player.find(
62 | // {
63 | // admin: false,
64 | // phantom: false,
65 | // disqualified: false
66 | // },
67 | // {
68 | // _id: 0,
69 | // username: 1,
70 | // level: 1
71 | // },
72 | // {
73 | // sort: {
74 | // level: -1,
75 | // lastLevelOn: 1
76 | // }
77 | // }
78 | // ).cache({ key: 'leaderboard' })
79 |
80 | // if (leaderboard.length < 24) {
81 | // playerName = 'nobody'
82 | // } else {
83 | // playerName = leaderboard[23].username.replace(/[\s]+/g, '').toLowerCase()
84 | // }
85 | // if (!(playerName === answer)) {
86 | // return res.json({
87 | // success: false,
88 | // message: constants.ERR_WRONG_ANS
89 | // })
90 | // }
91 | // } else {
92 | // // Regular level
93 | // const { answer: correctAnswer } = await Question.findOne({ level })
94 | // const hashedAttempt = hash(answer.toLowerCase())
95 |
96 | // if (hashedAttempt !== correctAnswer) {
97 | // return res.json({
98 | // success: false,
99 | // message: constants.ERR_WRONG_ANS
100 | // })
101 | // }
102 | // }
103 | // Regular level
104 | const { answer: correctAnswer } = await Question.findOne({ level })
105 | const hashedAttempt = hash(answer.toLowerCase())
106 |
107 | if (hashedAttempt !== correctAnswer) {
108 | return res.json({
109 | success: false,
110 | message: constants.ERR_WRONG_ANS
111 | })
112 | }
113 |
114 | // It's correct
115 | player.level = level + 1
116 | player.lastLevelOn = new Date()
117 | await player.save()
118 |
119 | log('ANSWER', 'Correct', `${username} @ L${level}: ${answer}`)
120 | clearCache('leaderboard')
121 |
122 | return res.json({
123 | success: true,
124 | message: constants.CORRECT_ANS
125 | })
126 | }
127 |
--------------------------------------------------------------------------------
/api/clear-cache.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import { clearCache } from '../src/cache'
3 | import { log } from '../src/utils'
4 |
5 | export default async (req, res) => {
6 | if (!req.user || !req.user.admin) {
7 | return res.json({
8 | success: false,
9 | message: constants.ERR_NOAUTH
10 | })
11 | }
12 |
13 | const { key } = req.body
14 | clearCache(key)
15 |
16 | log('ADMIN', `[${req.user.username}] Cache cleared`, key || 'FLUSH')
17 |
18 | return res.json({
19 | success: true,
20 | message: constants.GENERIC_SUCC
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/api/get-config.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import Config from '../models/config'
3 |
4 | // Config.create({
5 | // started: false,
6 | // showLeaderboard: false,
7 | // ended: false,
8 | // startDate: new Date(2022, 5, 28)
9 | // })
10 |
11 | export default async (req, res) => {
12 | const config = await Config.findOne({}, { _id: 0 }).cache({
13 | key: 'config'
14 | })
15 |
16 | return res.json({
17 | success: true,
18 | message: constants.GENERIC_SUCC,
19 | data: { config }
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/api/get-leaderboard.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import Player from '../models/player'
3 | import Config from '../models/config'
4 |
5 | export default async (req, res) => {
6 | const isAdmin = req.user && req.user.admin
7 |
8 | const config = await Config.findOne().cache({ key: 'config' })
9 | if (!config.showLeaderboard && !isAdmin) {
10 | return res.json({
11 | success: true,
12 | message: constants.GENERIC_SUCC,
13 | data: { leaderboard: [] }
14 | })
15 | }
16 |
17 | let leaderboard = await Player.find(
18 | {
19 | admin: false,
20 | phantom: false,
21 | disqualified: false
22 | },
23 | {
24 | _id: 0,
25 | username: 1,
26 | level: 1
27 | },
28 | {
29 | sort: {
30 | level: -1,
31 | lastLevelOn: 1
32 | }
33 | }
34 | ).cache({ key: 'leaderboard' })
35 |
36 | if (!config.ended && !isAdmin) {
37 | leaderboard = leaderboard.slice(0, constants.MAX_LEADERBOARD_PLAYERS)
38 | }
39 |
40 | return res.json({
41 | success: true,
42 | message: constants.GENERIC_SUCC,
43 | data: { leaderboard }
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/api/get-logs.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import Log from "../models/log";
3 |
4 | export default async (req, res) => {
5 | if (!req.user || !req.user.admin) {
6 | return res.json({
7 | success: false,
8 | message: constants.ERR_NOAUTH,
9 | });
10 | }
11 |
12 | const logs = await Log.find({}, { _id: 0, __v: 0 })
13 | .sort({ time: -1 })
14 | .limit(100);
15 |
16 | return res.json({
17 | success: true,
18 | message: constants.GENERIC_SUCC,
19 | data: { logs },
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/api/get-player-details.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import Player from "../models/player";
3 | import Log from "../models/log";
4 |
5 | export default async (req, res) => {
6 | if (!req.user || !req.user.admin) {
7 | return res.json({
8 | success: false,
9 | message: constants.ERR_NOAUTH,
10 | });
11 | }
12 |
13 | const { username } = req.body;
14 | const player = await Player.findOne({ username }).lean();
15 |
16 | if (!player) {
17 | return res.json({
18 | success: false,
19 | message: constants.ERR_NO_PLAYER,
20 | });
21 | }
22 |
23 | const { time } = await Log.findOne({ key: "Registered", value: username });
24 | player.registrationDate = time;
25 |
26 | return res.json({
27 | success: true,
28 | message: constants.GENERIC_SUCC,
29 | data: { player },
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/api/get-player-rank.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import Player from '../models/player'
3 |
4 | export default async (req, res) => {
5 | if (!req.user) {
6 | return res.json({
7 | success: false,
8 | message: constants.ERR_NOAUTH
9 | })
10 | }
11 | const { username } = req.user
12 |
13 | const leaderboard = await Player.find(
14 | {
15 | admin: false,
16 | phantom: false,
17 | disqualified: false
18 | },
19 | {
20 | _id: 0,
21 | username: 1,
22 | level: 1
23 | },
24 | {
25 | sort: {
26 | level: -1,
27 | lastLevelOn: 1
28 | }
29 | }
30 | ).cache({ key: 'leaderboard' })
31 |
32 | const rank = leaderboard.findIndex((p) => p.username === username) + 1
33 |
34 | if (rank === 0) {
35 | return res.json({
36 | success: false,
37 | message: constants.ERR_NO_PLAYER,
38 | data: { rank: -1 }
39 | })
40 | }
41 |
42 | return res.json({
43 | success: true,
44 | message: constants.GENERIC_SUCC,
45 | data: { rank }
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/api/get-question.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import Question from "../models/question";
3 | import { clearCache } from "../src/cache";
4 |
5 | export default async (req, res) => {
6 | if (!req.user) {
7 | return res.json({
8 | success: false,
9 | message: constants.ERR_NOAUTH,
10 | });
11 | }
12 |
13 | const level = req.user.admin ? req.body.level : req.user.level;
14 | const question = await Question.findOne({ level }).cache({
15 | key: `question_${level}`,
16 | });
17 |
18 | if (!question) {
19 | clearCache(`question_${level}`);
20 |
21 | if (req.user.admin) {
22 | return res.json({
23 | success: false,
24 | message: constants.ERR_NO_QUESTION,
25 | });
26 | }
27 |
28 | return res.json({
29 | success: true,
30 | message: constants.GENERIC_SUCC,
31 | data: { win: true },
32 | });
33 | }
34 |
35 | return res.json({
36 | success: true,
37 | message: constants.GENERIC_SUCC,
38 | data: {
39 | win: false,
40 | question: req.user.admin ? question : question.question,
41 | img: req.user.admin ? question : question.img
42 | },
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/api/get-stats.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import Player from "../models/player";
3 | import Question from "../models/question";
4 | import Log from "../models/log";
5 |
6 | export default async (req, res) => {
7 | if (!req.user || !req.user.admin) {
8 | return res.json({
9 | success: false,
10 | message: constants.ERR_NOAUTH,
11 | });
12 | }
13 |
14 | const playerCount = await Player.countDocuments({ admin: false });
15 | const adminCount = await Player.countDocuments({ admin: true });
16 | const answerAttempts = await Log.countDocuments({
17 | type: "ANSWER",
18 | key: "Attempt",
19 | });
20 | const lastTenRegistrants = await Log.find(
21 | { type: "AUTH", key: "Registered" },
22 | { _id: 0, value: 1, time: 1 }
23 | )
24 | .sort("-time")
25 | .limit(10);
26 |
27 | const playersPerLevel = [];
28 | const numberOfLevels = await Question.countDocuments();
29 | for (let level = 0; level < numberOfLevels; level++) {
30 | playersPerLevel.push(await Player.countDocuments({ level }));
31 | }
32 |
33 | const countries = await Player.find().distinct("geo.country");
34 | const cities = await Player.find().distinct("geo.city");
35 |
36 | return res.json({
37 | success: true,
38 | message: constants.GENERIC_SUCC,
39 | data: {
40 | playerCount,
41 | adminCount,
42 | answerAttempts,
43 | lastTenRegistrants,
44 | playersPerLevel,
45 | geo: { countries, cities },
46 | },
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/api/update-config.js:
--------------------------------------------------------------------------------
1 | import Config from "../models/config";
2 | import { clearCache } from "../src/cache";
3 | import { log } from "../src/utils";
4 |
5 | export default async (req, res) => {
6 | if (!req.user || !req.user.admin) {
7 | return res.redirect("/");
8 | }
9 |
10 | const config = await Config.findOne();
11 | const { action } = req.body;
12 |
13 | switch (action) {
14 | case "begin":
15 | config.started = true;
16 | break;
17 | case "end":
18 | config.ended = true;
19 | break;
20 | case "show-leaderboard":
21 | config.showLeaderboard = true;
22 | break;
23 | case "hide-leaderboard":
24 | config.showLeaderboard = false;
25 | break;
26 | }
27 |
28 | await config.save();
29 |
30 | clearCache("config");
31 | log("ADMIN", `[${req.user.username}] Danger action`, action);
32 |
33 | res.redirect("/");
34 | };
35 |
--------------------------------------------------------------------------------
/api/update-player-level.js:
--------------------------------------------------------------------------------
1 | import * as constants from '../src/constants'
2 | import { clearCache } from '../src/cache'
3 | import { log } from '../src/utils'
4 | import Player from '../models/player'
5 |
6 | export default async (req, res) => {
7 | if (!req.user || !req.user.admin) {
8 | return res.json({
9 | success: false,
10 | message: constants.ERR_NOAUTH
11 | })
12 | }
13 |
14 | const { username, newLevel } = req.body
15 | const player = await Player.findOne({ username })
16 | const { level } = player
17 |
18 | if (!player) {
19 | return res.json({
20 | success: false,
21 | message: constants.ERR_NO_PLAYER
22 | })
23 | }
24 |
25 | player.level = newLevel
26 | player.lastLevelOn = new Date()
27 | await player.save()
28 |
29 | clearCache('leaderboard')
30 | log(
31 | 'ADMIN',
32 | `[${req.user.username}] Player level updated`,
33 | `${username}: ${level} -> ${newLevel}`
34 | )
35 |
36 | return res.json({
37 | success: true,
38 | message: constants.GENERIC_SUCC
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/api/update-player-type.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import { clearCache } from "../src/cache";
3 | import { log } from "../src/utils";
4 | import Player from "../models/player";
5 |
6 | const types = ["admin", "superadmin", "phantom", "disqualified"];
7 |
8 | export default async (req, res) => {
9 | if (!req.user || !req.user.admin) {
10 | return res.json({
11 | success: false,
12 | message: constants.ERR_NOAUTH,
13 | });
14 | }
15 |
16 | const { username, type, value } = req.body;
17 | const player = await Player.findOne({ username });
18 |
19 | if (!player) {
20 | return res.json({
21 | success: false,
22 | message: constants.ERR_NO_PLAYER,
23 | });
24 | }
25 |
26 | if (!types.includes(type)) {
27 | return res.json({
28 | success: false,
29 | message: constants.ERR_MISC,
30 | });
31 | }
32 |
33 | player[type] = Number(value);
34 | await player.save();
35 |
36 | clearCache("leaderboard");
37 | log(
38 | "ADMIN",
39 | `[${req.user.username}] Player type changed`,
40 | `${username}: ${type} -> ${Boolean(Number(value))}`
41 | );
42 |
43 | return res.json({
44 | success: true,
45 | message: constants.GENERIC_SUCC,
46 | });
47 | };
48 |
--------------------------------------------------------------------------------
/api/update-question.js:
--------------------------------------------------------------------------------
1 | import * as constants from "../src/constants";
2 | import { clearCache } from "../src/cache";
3 | import { log, hash } from "../src/utils";
4 | import Question from "../models/question";
5 |
6 | export default async (req, res) => {
7 | if (!req.user || !req.user.admin) {
8 | return res.json({
9 | success: false,
10 | message: constants.ERR_NOAUTH,
11 | });
12 | }
13 |
14 | const { level, question: questionText, img, answer } = req.body;
15 | const question = await Question.findOne({ level });
16 |
17 | if (!question) {
18 | return res.json({
19 | success: false,
20 | message: constants.ERR_NO_QUESTION,
21 | });
22 | }
23 |
24 | question.question = questionText;
25 | question.img = img;
26 | question.answer = hash(answer.replace(/[\s]+/g, "").toLowerCase());
27 | await question.save();
28 |
29 | clearCache(`question_${level}`);
30 | log("ADMIN", `[${req.user.username}] Question updated`, `Level ${level}`);
31 |
32 | return res.json({
33 | success: true,
34 | message: constants.GENERIC_SUCC,
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
Slash Register // Slash
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
30 |
31 |
32 |
33 | Register
34 | Create an account to start playing. Login if you already
35 | have.
36 |
37 |
43 |
--------------------------------------------------------------------------------
/models/config.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const ConfigSchema = new mongoose.Schema({
4 | started: Boolean,
5 | showLeaderboard: Boolean,
6 | ended: Boolean,
7 | startDate: Date,
8 | });
9 |
10 | export default mongoose.model("Config", ConfigSchema, "config");
11 |
--------------------------------------------------------------------------------
/models/log.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const LogSchema = new mongoose.Schema({
4 | type: String,
5 | key: String,
6 | value: String,
7 | time: {
8 | type: Date,
9 | default: Date.now,
10 | },
11 | });
12 |
13 | export default mongoose.model("Log", LogSchema);
14 |
--------------------------------------------------------------------------------
/models/player.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | import passportLocalMongoose from 'passport-local-mongoose'
3 |
4 | const PlayerSchema = new mongoose.Schema({
5 | username: {
6 | type: String,
7 | unique: true
8 | },
9 | disqualified: {
10 | type: Boolean,
11 | default: false
12 | },
13 | level: {
14 | type: Number,
15 | default: 0
16 | },
17 | lastLevelOn: {
18 | type: Date,
19 | default: Date.now
20 | },
21 | phantom: {
22 | type: Boolean,
23 | default: false
24 | },
25 | admin: {
26 | type: Boolean,
27 | default: false
28 | },
29 | superadmin: {
30 | type: Boolean,
31 | default: false
32 | },
33 | geo: {
34 | country: String,
35 | city: String
36 | }
37 | })
38 |
39 | PlayerSchema.plugin(passportLocalMongoose)
40 |
41 | export default mongoose.model('Player', PlayerSchema)
42 |
--------------------------------------------------------------------------------
/models/question.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const QuestionSchema = new mongoose.Schema({
4 | level: {
5 | type: Number,
6 | unique: true,
7 | },
8 | question: String,
9 | img: String,
10 | answer: String,
11 | });
12 |
13 | export default mongoose.model("Question", QuestionSchema);
14 |
--------------------------------------------------------------------------------
/mongo.js:
--------------------------------------------------------------------------------
1 | // Clear and Restart DB
2 | db.dropDatabase()
3 | use slash
4 |
5 | // CLear and Reset Slash config
6 | db.config.drop()
7 | db.config.insert({
8 | started: false,
9 | showLeaderboard: false,
10 | ended: false,
11 | startDate: new Date(2023, 0, 8).setHours(12, 30)
12 | })
13 |
14 | //Set Admin and super admin details
15 | db.players.update({"username" : "superadmin"}, {$set: {"superadmin" : true,"admin": true}})
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slash",
3 | "description": "Platform for Slash Hunt",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "sapper dev",
8 | "build": "sapper build --legacy",
9 | "start": "node __sapper__/build",
10 | "lint": "eslint . --ext .js,.svelte",
11 | "lint:fix": "eslint . --ext .js,.svelte --fix"
12 | },
13 | "dependencies": {
14 | "body-parser": "^1.19.0",
15 | "connect-mongo": "^3.2.0",
16 | "email-validator": "^2.0.4",
17 | "express": "^4.17.1",
18 | "express-rate-limit": "^5.1.3",
19 | "express-session": "^1.17.1",
20 | "mongoose": "^5.10.9",
21 | "node-fetch": "^2.6.1",
22 | "passport": "^0.4.1",
23 | "passport-local": "^1.0.0",
24 | "passport-local-mongoose": "^6.0.1",
25 | "redis": "^3.0.2",
26 | "timeago.js": "^4.0.2"
27 | },
28 | "devDependencies": {
29 | "@rollup/plugin-commonjs": "^15.1.0",
30 | "@rollup/plugin-node-resolve": "^9.0.0",
31 | "@rollup/plugin-replace": "^2.3.3",
32 | "autoprefixer": "^9.8.6",
33 | "cssnano": "^4.1.10",
34 | "dotenv": "^8.2.0",
35 | "eslint": "^7.11.0",
36 | "eslint-config-standard": "^14.1.1",
37 | "eslint-plugin-import": "^2.22.1",
38 | "eslint-plugin-node": "^11.1.0",
39 | "eslint-plugin-promise": "^4.2.1",
40 | "eslint-plugin-standard": "^4.0.1",
41 | "eslint-plugin-svelte3": "^2.7.3",
42 | "node-sass": "^4.14.1",
43 | "rollup": "^2.29.0",
44 | "rollup-plugin-svelte": "^6.0.1",
45 | "rollup-plugin-terser": "^7.0.2",
46 | "sapper": "^0.28.10",
47 | "sapper-page-loading-bar": "^1.0.0",
48 | "svelte": "^3.29.0",
49 | "svelte-preprocess": "^4.5.1"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from "@rollup/plugin-node-resolve";
2 | import replace from "@rollup/plugin-replace";
3 | import commonjs from "@rollup/plugin-commonjs";
4 | import svelte from "rollup-plugin-svelte";
5 | import { terser } from "rollup-plugin-terser";
6 | import config from "sapper/config/rollup.js";
7 | import pkg from "./package.json";
8 | import sveltePreprocess from "svelte-preprocess";
9 | import autoprefixer from "autoprefixer";
10 | import cssnano from "cssnano";
11 |
12 | const mode = process.env.NODE_ENV;
13 | const dev = mode === "development";
14 |
15 | const preprocess = sveltePreprocess({
16 | scss: {
17 | includePaths: ["src"],
18 | },
19 | postcss: {
20 | plugins: [autoprefixer, cssnano],
21 | },
22 | });
23 |
24 | const onwarn = (warning, onwarn) => {
25 | if (warning.code === "CIRCULAR_DEPENDENCY") return;
26 | return (
27 | (warning.code === "MISSING_EXPORT" && /'preload'/.test(warning.message)) ||
28 | (warning.code === "CIRCULAR_DEPENDENCY" &&
29 | /[/\\]@sapper[/\\]/.test(warning.message)) ||
30 | onwarn(warning)
31 | );
32 | };
33 |
34 | export default {
35 | client: {
36 | input: config.client.input(),
37 | output: config.client.output(),
38 | plugins: [
39 | replace({
40 | "process.browser": true,
41 | "process.env.NODE_ENV": JSON.stringify(mode),
42 | }),
43 | svelte({
44 | dev,
45 | hydratable: true,
46 | preprocess,
47 | emitCss: true,
48 | }),
49 | resolve({
50 | browser: true,
51 | dedupe: ["svelte"],
52 | }),
53 | commonjs(),
54 |
55 | !dev &&
56 | terser({
57 | module: true,
58 | }),
59 | ],
60 | preserveEntrySignatures: "strict",
61 | onwarn,
62 | },
63 |
64 | server: {
65 | input: config.server.input(),
66 | output: config.server.output(),
67 | plugins: [
68 | replace({
69 | "process.browser": false,
70 | "process.env.NODE_ENV": JSON.stringify(mode),
71 | }),
72 | svelte({
73 | generate: "ssr",
74 | hydratable: true,
75 | preprocess,
76 | dev,
77 | }),
78 | resolve({
79 | dedupe: ["svelte"],
80 | }),
81 | commonjs(),
82 | ],
83 | external: Object.keys(pkg.dependencies).concat(
84 | // eslint-disable-next-line node/no-deprecated-api
85 | require("module").builtinModules ||
86 | Object.keys(process.binding("natives"))
87 | ),
88 | preserveEntrySignatures: "strict",
89 | onwarn,
90 | },
91 | };
92 |
--------------------------------------------------------------------------------
/routers/api.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | import addQuestion from '../api/add-question'
4 | import checkAnswer from '../api/check-answer'
5 | import clearCache from '../api/clear-cache'
6 | import getConfig from '../api/get-config'
7 | import getLeaderboard from '../api/get-leaderboard'
8 | import getPlayerDetails from '../api/get-player-details'
9 | import getPlayerRank from '../api/get-player-rank'
10 | import getQuestion from '../api/get-question'
11 | import getLogs from '../api/get-logs'
12 | import getStats from '../api/get-stats'
13 | import updateConfig from '../api/update-config'
14 | import updatePlayerLevel from '../api/update-player-level'
15 | import updatePlayerType from '../api/update-player-type'
16 | import updateQuestion from '../api/update-question'
17 |
18 | const router = express.Router()
19 |
20 | router.get('/get-config', getConfig)
21 | router.get('/get-leaderboard', getLeaderboard)
22 | router.get('/get-player-rank', getPlayerRank)
23 | router.get('/get-question', getQuestion)
24 | router.get('/get-logs', getLogs)
25 | router.get('/get-stats', getStats)
26 | router.post('/add-question', addQuestion)
27 | router.post('/check-answer', checkAnswer)
28 | router.post('/clear-cache', clearCache)
29 | router.post('/get-player-details', getPlayerDetails)
30 | router.post('/get-question', getQuestion)
31 | router.post('/update-config', updateConfig)
32 | router.post('/update-player-level', updatePlayerLevel)
33 | router.post('/update-player-type', updatePlayerType)
34 | router.post('/update-question', updateQuestion)
35 |
36 | export default router
37 |
--------------------------------------------------------------------------------
/routers/auth.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import passport from 'passport'
3 |
4 | import Player from '../models/player'
5 | import * as constants from '../src/constants'
6 | import { clearCache } from '../src/cache'
7 | import { log, getGeoInfo } from '../src/utils'
8 |
9 | function login (player, req) {
10 | const response = {}
11 |
12 | req.logIn(player, (err) => {
13 | if (err) {
14 | response.success = false
15 | response.message = constants.ERR_MISC
16 | return
17 | }
18 |
19 | log('AUTH', 'Logged in', player.username)
20 |
21 | response.success = true
22 | response.message = constants.LOGIN_SUCCESS
23 | })
24 |
25 | return response
26 | }
27 |
28 | const router = express.Router()
29 |
30 | // User logout at /auth/logout
31 | router.get('/logout', (req, res) => {
32 | log('AUTH', 'Logged out', req.user.username)
33 |
34 | req.logout()
35 | res.redirect('/')
36 | })
37 |
38 | // User login at /auth/login
39 | router.post('/login', (req, res) => {
40 | let response = {
41 | success: false,
42 | message: undefined
43 | }
44 |
45 | passport.authenticate('local', (err, player) => {
46 | if (err) {
47 | response.message = err.message
48 | return res.json(response)
49 | }
50 |
51 | if (!player) {
52 | response.message = constants.ERR_INVALID_CREDS
53 | return res.json(response)
54 | }
55 |
56 | if (player.disqualified) {
57 | response.message = constants.ERR_PLAYER_DQ
58 | return res.json(response)
59 | }
60 |
61 | response = login(player, req)
62 | return res.json(response)
63 | })(req, res)
64 | })
65 |
66 | // User register at /auth/register
67 | router.post('/register', async (req, res) => {
68 | let response = {
69 | success: false,
70 | message: undefined
71 | }
72 |
73 | const { username, password } = req.body
74 |
75 | if (
76 | !constants.ANSWER_REGEX.test(username) ||
77 | !constants.USERNAME_LENGTH_REGEX.test(username)
78 | ) {
79 | response.message = constants.ERR_INVALID_EMAIL_USERNAME
80 | return res.json(response)
81 | }
82 |
83 | const existingUser = await Player.findOne({ username })
84 | if (existingUser) {
85 | response.message = constants.ERR_USERNAME_EXISTS
86 | return res.json(response)
87 | }
88 |
89 | const geo = await getGeoInfo(req)
90 | const player = await Player.register({ username, geo }, password)
91 |
92 | log('AUTH', 'Registered', username)
93 | clearCache('leaderboard')
94 |
95 | response = login(player, req)
96 | return res.json(response)
97 | })
98 |
99 | export default router
100 |
--------------------------------------------------------------------------------
/src/cache.js:
--------------------------------------------------------------------------------
1 | import dotenv from "dotenv";
2 | import { promisify } from "util";
3 | import redis from "redis";
4 | import mongoose from "mongoose";
5 | import { log } from "./utils";
6 |
7 | dotenv.config();
8 |
9 | const { REDIS_HOST, REDIS_PORT } = process.env;
10 |
11 | // redis
12 | const redisClient = redis.createClient({
13 | host: REDIS_HOST,
14 | port: REDIS_PORT,
15 | retry_strategy: () => 1000,
16 | });
17 |
18 | redisClient.get = promisify(redisClient.get);
19 | const mongoExec = mongoose.Query.prototype.exec;
20 |
21 | mongoose.Query.prototype.cache = function (options) {
22 | this.useCache = true;
23 | this.time = options.time;
24 | this.key = options.key;
25 | this.createDocument = options.createDocument;
26 | return this;
27 | };
28 |
29 | mongoose.Query.prototype.exec = async function () {
30 | if (!this.useCache) {
31 | return await mongoExec.apply(this, arguments);
32 | }
33 |
34 | const { key } = this;
35 | const cacheValue = await redisClient.get(key);
36 |
37 | if (cacheValue) {
38 | const doc = JSON.parse(cacheValue);
39 |
40 | /* eslint-disable new-cap */
41 | if (this.createDocument) {
42 | return Array.isArray(doc)
43 | ? doc.map((d) => new this.model(d))
44 | : new this.model(doc);
45 | }
46 |
47 | return doc;
48 | }
49 |
50 | const result = await mongoExec.apply(this, arguments);
51 |
52 | redisClient.set(key, JSON.stringify(result));
53 | log("CACHE", "Set", key);
54 |
55 | if (this.time) {
56 | redisClient.expire(key, this.time, () => {});
57 | }
58 |
59 | return result;
60 | };
61 |
62 | export function clearCache(key) {
63 | if (key) {
64 | redisClient.del(key);
65 | log("CACHE", "Cleared", key);
66 | } else {
67 | const success = redisClient.flushall();
68 | log("CACHE", "Flushed all keys", success);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from "@sapper/app";
2 |
3 | sapper.start({
4 | target: document.body,
5 | });
6 |
7 | // Unregister all service workers from client's devices
8 | // Kuch nahi kar rahe vo apart from caching which I don't
9 | // really need and the browsers are already doing their bit
10 | if ("serviceWorker" in navigator) {
11 | navigator.serviceWorker.getRegistrations().then(function (registrations) {
12 | for (const registration of registrations) {
13 | registration.unregister();
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/AdminForm.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
70 |
71 |
99 |
--------------------------------------------------------------------------------
/src/components/Chevron.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/src/components/Countdown.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 | {days}
43 | d
44 |
45 | {hours < 10 ? "0" + hours : hours}
46 | h
47 |
48 | {minutes < 10 ? "0" + minutes : minutes}
49 | m
50 |
51 | {seconds < 10 ? "0" + seconds : seconds}
52 | s
53 |
54 |
55 |
73 |
--------------------------------------------------------------------------------
/src/components/Navigation.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
77 |
78 |
174 |
--------------------------------------------------------------------------------
/src/components/play/Footer.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Level {level}
9 | {username}
10 | Rank: {rank}
11 |
12 |
13 |
47 |
--------------------------------------------------------------------------------
/src/components/play/GameArea.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 | {#if !config.started}
16 |
Hang tight!
17 |
Enigma will begin in
18 |
19 |
20 | Till then, join our
21 | Discord server .
22 |
23 | {:else if win}
24 |
Congratulations!
25 |
26 | You have successfully completed Slash with a rank of {rank} !
29 |
30 |
31 | Go to the leaderboard to check the standings.
32 |
33 | {:else if config.ended}
34 |
The End!
35 |
36 | Enigma has come to an end! You have finished with a rank of {rank} .
39 |
40 |
41 | Go to the leaderboard to check the standings.
42 |
43 | {:else}
44 |
Question
45 |
46 | {/if}
47 |
48 |
49 |
50 |
94 |
--------------------------------------------------------------------------------
/src/components/play/Navigation.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 | Leaderboard
19 |
20 | Discord
23 |
24 | Logout
25 |
26 |
27 |
28 |
36 |
37 | {#if showMenu}
38 |
47 | {/if}
48 |
49 |
50 |
51 |
150 |
--------------------------------------------------------------------------------
/src/components/play/QuestionAnswer.svelte:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 | {@html question}
61 |
62 |
82 |
83 |
187 |
--------------------------------------------------------------------------------
/src/components/play/SidePanel.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
74 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const ANSWER_REGEX = /^\S+$/;
2 | export const USERNAME_LENGTH_REGEX = /^.{2,25}$/;
3 | export const COOKIE_MAX_AGE = 2592000000;
4 | export const MAX_LEADERBOARD_PLAYERS = 50;
5 |
6 | // Response success
7 | export const CORRECT_ANS = "Correct answer";
8 | export const GENERIC_SUCC = "Successful request";
9 | export const LOGIN_SUCCESS = "Successful, redirecting...";
10 |
11 | // Response failure
12 | export const ERR_NOAUTH = "Request unauthenticated";
13 | export const ERR_INVALID_ANS =
14 | "No whitespace allowed. Further invalid attempts will lead to disqualification.";
15 | export const ERR_WRONG_ANS = "Wrong answer";
16 | export const ERR_MISC = "An error occured while processing the query";
17 | export const ERR_NO_PLAYER = "Player with requested ID not found";
18 | export const ERR_NO_QUESTION = "Question could not be found";
19 | export const ERR_LEVEL_EXISTS = "Question for specified level exists";
20 | export const ERR_INVALID_CREDS = "Wrong player username or password";
21 | export const ERR_INVALID_EMAIL_USERNAME = "Invalid username";
22 | export const ERR_USERNAME_EXISTS = "Username taken";
23 | export const ERR_EMAIL_EXISTS =
24 | "Email is already in use. Please try another email or login using existing account";
25 | export const ERR_PLAYER_DQ = "You have been disqualified";
26 | export const HUNT_END = "The hunt has ended";
27 |
--------------------------------------------------------------------------------
/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {status}
10 |
11 |
12 | {status}
13 |
14 | {error.message}
15 |
16 | {#if dev && error.stack}
17 | {error.stack}
18 | {/if}
19 |
20 |
42 |
--------------------------------------------------------------------------------
/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
18 |
19 |
20 | {#if !$session.user || !$session.user.admin}
21 |
29 |
34 | {/if}
35 |
36 |
37 |
38 |
39 | {#if segment}
40 |
41 | {/if}
42 |
43 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/src/routes/admin.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
22 |
23 |
24 | Admin // Enigma
25 |
26 |
27 |
28 |
29 | Admin Panel
30 |
31 | View statistics
32 | {#if (admin || superadmin)} View logs {/if}
33 |
34 |
180 |
181 |
192 |
193 |
202 |
203 |
204 |
205 |
277 |
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
58 |
59 |
60 |
61 |
62 | {#if config.started}
63 | Level {level} // Enigma
64 | {/if}
65 |
66 |
67 |
68 | {@html ''}
69 | {#if innerWidth > 800}
70 |
71 | {/if}
72 |
73 |
74 |
75 |
76 |
77 |
78 | {#if innerWidth <= 800}
79 |
80 | {/if}
81 |
82 |
83 |
84 |
97 |
--------------------------------------------------------------------------------
/src/routes/johnwickr.html:
--------------------------------------------------------------------------------
1 |
2 | Well, John Wasn't Exactly 'The Boogeyman .' He Was The One You Sent To Kill The Fucking Boogeyman.
3 |
--------------------------------------------------------------------------------
/src/routes/leaderboard.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
29 |
30 |
31 | Leaderboard // Enigma
32 |
33 |
34 |
35 | Leaderboard
36 |
37 | {#if config.showLeaderboard || isAdmin}
38 | {#if !config.ended && !isAdmin}
39 |
40 | The leaderboard is displaying only the top {MAX_LEADERBOARD_PLAYERS} players.
41 |
42 | {/if}
43 |
44 |
45 |
46 |
47 | Rank
48 | Player
49 | Level
50 |
51 |
52 |
53 | {#each leaderboard as entry, i}
54 | {#if entry}
55 |
56 | {i + 1}
57 | {entry.username}
58 | {entry.level}
59 |
60 | {/if}
61 | {/each}
62 |
63 |
64 | {:else}
65 |
66 | The leaderboard will activate {config.started
67 | ? "soon"
68 | : "once Enigma begins"}.
69 |
70 |
71 | Till then, join our
72 | Discord server .
73 |
74 | {/if}
75 |
76 |
77 |
152 |
--------------------------------------------------------------------------------
/src/routes/login.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
40 |
41 |
42 | Login // Enigma
43 |
44 |
45 |
46 |
47 | Login
48 | Are you ready for a adrenaline-pumping race through the world of mysteries and enigmas? Iliad is an online hunt that takes you on a journey through the internet and beyond with fuzzling challenges, bewildering mysteries, and conundrums to be solved. Solve cryptic messages, find signs hidden in plain sight and bring out the pattern-seeker inside you to climb up the leaderboard and win.
49 |
50 |
83 | Login or Register to start playing.
84 |
85 |
86 |
87 |
106 |
--------------------------------------------------------------------------------
/src/routes/logs.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
28 |
29 |
30 | Logs // Enigma
31 |
32 |
33 |
34 |
57 |
58 |
59 |
139 |
--------------------------------------------------------------------------------
/src/routes/register.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
42 |
43 |
44 | Register // Enigma
45 |
46 |
47 |
48 |
49 | Register
50 |
51 | Create an account to start playing. Login if you already
52 | have.
53 |
54 |
96 |
97 |
98 |
99 |
108 |
--------------------------------------------------------------------------------
/src/routes/stats.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
19 |
20 |
21 | Stats // Enigma
22 |
23 |
24 |
25 |
26 | Stats
27 |
28 |
29 |
{data.answerAttempts}
30 |
Answer attempts
31 |
32 |
33 |
34 |
Players per level
35 |
36 |
37 |
38 | Level
39 | Players
40 |
41 |
42 | {#each data.playersPerLevel as count, level}
43 |
44 | {level}
45 | {count}
46 |
47 | {/each}
48 |
49 |
50 |
51 |
52 |
{data.playerCount}
53 |
Players
54 |
55 |
56 |
57 |
{data.adminCount}
58 |
Admins
59 |
60 |
61 |
62 |
{data.geo.countries.length}
63 |
Countries
64 |
71 |
72 |
73 |
74 |
{data.geo.cities.length}
75 |
Cities
76 |
83 |
84 |
85 |
86 |
Last 10 registrants
87 |
88 | {#each data.lastTenRegistrants as entry}
89 |
90 | {entry.value}
91 | {format(entry.time)}
92 |
93 | {/each}
94 |
95 |
96 |
97 |
98 |
99 |
100 |
201 |
--------------------------------------------------------------------------------
/src/routes/thatsonesmallstepformanonegiantleapformankind.html:
--------------------------------------------------------------------------------
1 | prediction '45
--------------------------------------------------------------------------------
/src/routes/thetis.svelte:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv'
2 | import * as sapper from '@sapper/server'
3 | import bodyParser from 'body-parser'
4 | import express from 'express'
5 | import rateLimit from 'express-rate-limit'
6 | import session from 'express-session'
7 | import passport from 'passport'
8 | import mongoose from 'mongoose'
9 | import connectMongo from 'connect-mongo'
10 |
11 | import './cache'
12 | import * as constants from './constants'
13 | import Player from '../models/player'
14 | import api from '../routers/api'
15 | import auth from '../routers/auth'
16 |
17 | dotenv.config()
18 |
19 | // get those vars
20 | const { PORT, SESSION_SECRET, MONGODB_CONNECTION_URI } = process.env
21 |
22 | // instantiate express app
23 | const app = express()
24 |
25 | // Mongoose connection
26 | mongoose.set('useCreateIndex', true)
27 | mongoose.connect(MONGODB_CONNECTION_URI, {
28 | useNewUrlParser: true,
29 | useUnifiedTopology: true
30 | })
31 |
32 | const MongoStore = connectMongo(session)
33 |
34 | // use express-session to store sessions
35 | app.use(
36 | session({
37 | secret: SESSION_SECRET,
38 | resave: false,
39 | saveUninitialized: false,
40 | cookie: {
41 | maxAge: constants.COOKIE_MAX_AGE
42 | },
43 | store: new MongoStore({
44 | mongooseConnection: mongoose.connection
45 | })
46 | })
47 | )
48 |
49 | app.use(passport.initialize())
50 | app.use(passport.session())
51 |
52 | passport.use(Player.createStrategy())
53 | passport.serializeUser(Player.serializeUser())
54 | passport.deserializeUser(Player.deserializeUser())
55 |
56 | app.set('trust proxy', true)
57 |
58 | // Setting up body-parser
59 | app.use(bodyParser.urlencoded({ extended: true }))
60 | app.use(bodyParser.json())
61 |
62 | // the static content from /static
63 | app.use(express.static('static'))
64 |
65 | // Rate limiter for auth requests
66 | // defaulting to memory store because window
67 | // of limit is low and data will be expired
68 | // and chucked quickly
69 | app.use(
70 | '/auth',
71 | rateLimit({
72 | windowMs: 60 * 1000,
73 | max: 10
74 | })
75 | )
76 |
77 | // Routes
78 | app.use('/auth', auth)
79 | app.use('/api', api)
80 | app.use('/player/:username', (req, res) => res.end())
81 |
82 | // the sapper stuff
83 | app.use(
84 | sapper.middleware({
85 | session: (req, res) => {
86 | res.setHeader('cache-control', 'no-cache, no-store')
87 | if (req.user) {
88 | const { username, admin, superadmin, level, disqualified } = req.user
89 | return {
90 | user: {
91 | username,
92 | admin,
93 | superadmin,
94 | level,
95 | disqualified
96 | }
97 | }
98 | }
99 | return {}
100 | }
101 | })
102 | )
103 |
104 | // finally listen
105 | app.listen({ port: PORT }, () =>
106 | console.log(`Server ready at http://localhost:${PORT}`)
107 | )
108 |
--------------------------------------------------------------------------------
/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Enigma
5 | %sapper.base%
6 |
7 |
8 |
9 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
54 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
78 | %sapper.styles%
79 |
80 |
81 | %sapper.head%
82 |
83 |
84 | %sapper.scripts%
85 |
86 |
87 |
88 |
89 |
90 | %sapper.html%
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { createHash } from 'crypto'
2 | import fetch from 'node-fetch'
3 | import Log from '../models/log'
4 |
5 | export async function wait (ms) {
6 | await new Promise((resolve, reject) => setTimeout(resolve, ms))
7 | }
8 |
9 | export function log (type, key, value) {
10 | Log.create({ type, key, value })
11 | console.log('%d: [%s] %s: %s', Date.now(), type, key, value)
12 | }
13 |
14 | export async function getGeoInfo (req) {
15 | const ip = req.headers['x-real-ip']
16 | const res = await fetch(`http://ip-api.com/json/${ip}?fields=17`)
17 | return await res.json()
18 | }
19 |
20 | export function hash (string) {
21 | return createHash('sha1').update(string).digest('hex')
22 | }
23 |
--------------------------------------------------------------------------------
/static/Illiad logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/Illiad logo.png
--------------------------------------------------------------------------------
/static/loading.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
13 |
14 |
18 |
22 |
23 |
24 |
28 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/static/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/static/meta/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/meta/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/android-chrome-512x512.png
--------------------------------------------------------------------------------
/static/meta/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/meta/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #b91d47
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/static/meta/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/favicon-16x16.png
--------------------------------------------------------------------------------
/static/meta/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/favicon-32x32.png
--------------------------------------------------------------------------------
/static/meta/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/favicon.ico
--------------------------------------------------------------------------------
/static/meta/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Slash",
3 | "short_name": "Slash",
4 | "icons": [
5 | {
6 | "src": "/meta/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/meta/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "background_color": "#ffffff",
17 | "theme_color": "#333333",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/static/meta/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/mstile-150x150.png
--------------------------------------------------------------------------------
/static/meta/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
92 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/static/meta/site-card-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/site-card-old.png
--------------------------------------------------------------------------------
/static/meta/site-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/meta/site-card.png
--------------------------------------------------------------------------------
/static/paradoxf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/static/paradoxf.png
--------------------------------------------------------------------------------
/static/robots.txt:
--------------------------------------------------------------------------------
1 | # Je suis banana man
2 | User-Agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/styles/admin-forms.scss:
--------------------------------------------------------------------------------
1 | form.admin {
2 | padding: 5px 20px;
3 | border-radius: 10px;
4 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
5 | min-width: 300px;
6 | margin: 20px;
7 | flex: 1;
8 | background: #1f2122;
9 |
10 | h2 {
11 | margin-top: 10px;
12 | font-size: 22px;
13 | }
14 |
15 | p.general {
16 | font-size: 16px;
17 | color: rgb(130, 130, 130);
18 | line-height: 1.7;
19 | margin-top: 20px;
20 | }
21 |
22 | .input-grp {
23 | margin: 20px 0;
24 | display: flex;
25 | flex-direction: column;
26 |
27 | > label {
28 | font-size: 14px;
29 | color: #666;
30 | padding-bottom: 10px;
31 | line-height: 1.5;
32 | }
33 |
34 | input,
35 | button,
36 | select,
37 | textarea {
38 | background: none;
39 | border-radius: 5px;
40 | border: 1px solid #ffffff0f;
41 | padding: 10px;
42 | font-family: Inter, sans-serif;
43 | font-size: 18px;
44 | color: #bbb;
45 | transition: all 0.1s linear;
46 |
47 | &:focus,
48 | &:active {
49 | border-color: #ffffff29;
50 | outline: none;
51 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
52 | }
53 |
54 | &:hover {
55 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
56 | }
57 | }
58 |
59 | button {
60 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12);
61 | border: none;
62 | width: fit-content;
63 | cursor: pointer;
64 | background: #181a1b;
65 | font-size: 16px;
66 | color: #fff;
67 | }
68 |
69 | select {
70 | width: fit-content;
71 | color: #ddd;
72 | }
73 |
74 | .radio {
75 | display: flex;
76 |
77 | label {
78 | margin-right: 10px;
79 | font-size: 16px;
80 | color: #bbb;
81 | display: flex;
82 | align-items: center;
83 | }
84 |
85 | input {
86 | margin-right: 5px;
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/styles/auth-forms.scss:
--------------------------------------------------------------------------------
1 | @import "../../styles/theme.scss";
2 |
3 | section {
4 | width: 85%;
5 | max-width: 600px;
6 | margin: 15vh auto 50px;
7 |
8 | h1 {
9 | margin-bottom: 2vh;
10 | font-size: 36px;
11 | display: flex;
12 | align-items: flex-end;
13 |
14 | &:after {
15 | content: "";
16 | flex: 1;
17 | height: 5px;
18 | margin: 0 0 10px 20px;
19 | background: #461f26;
20 | }
21 | }
22 |
23 | p {
24 | font-size: 18px;
25 | color: #828282;
26 | line-height: 1.7;
27 | }
28 |
29 | a {
30 | color: #bbb;
31 | }
32 | }
33 |
34 | form {
35 | padding: 5px 20px;
36 | border-radius: 10px;
37 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
38 | width: 100%;
39 | max-width: 400px;
40 | margin: 8vh auto 0;
41 | background: #1f2122;
42 |
43 | p.message {
44 | margin-top: 20px;
45 | display: block;
46 | text-align: center;
47 | line-height: 1.5;
48 | font-weight: 500;
49 | font-size: 16px;
50 | color: #71a271;
51 |
52 | &.error {
53 | color: #cf6f6f;
54 | }
55 | }
56 |
57 | .input-grp {
58 | margin: 20px 0;
59 | display: flex;
60 | flex-direction: column;
61 |
62 | &:focus-within label {
63 | color: #aaa;
64 | }
65 |
66 | label {
67 | font-size: 14px;
68 | color: #666;
69 | padding-bottom: 10px;
70 | line-height: 1.5;
71 | transition: 0.1s all ease-in-out;
72 | }
73 |
74 | input,
75 | button,
76 | select {
77 | background: none;
78 | border-radius: 5px;
79 | border: 1px solid #ffffff0f;
80 | padding: 10px;
81 | font-family: Inter, sans-serif;
82 | font-size: 18px;
83 | color: #bbb;
84 | transition: all 0.1s linear;
85 |
86 | &:focus,
87 | &:active {
88 | border-color: #ffffff29;
89 | outline: none;
90 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
91 | }
92 |
93 | &:hover {
94 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
95 | }
96 | }
97 | }
98 |
99 | button {
100 | width: fit-content;
101 | cursor: pointer;
102 | background: $color-accent !important;
103 | color: #fff !important;
104 | align-self: center;
105 |
106 | &:hover {
107 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
108 | }
109 |
110 | img {
111 | height: 7px;
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/styles/common.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IEEE-IIIT-Delhi/slash-platform/845dce4984277a3941aea093d425d399ce2dd473/styles/common.scss
--------------------------------------------------------------------------------
/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import "./theme.scss";
2 |
3 | html,
4 | body,
5 | :root {
6 | width: 100%;
7 | min-height: -webkit-fill-available;
8 | font-size: 16px;
9 | font-weight: 400;
10 | font-family: $font-default;
11 | color: $color-text;
12 | background: $color-bg;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-overflow-scrolling: touch;
16 | }
17 |
18 | body {
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | }
23 |
24 | * {
25 | padding: 0;
26 | margin: 0;
27 | box-sizing: border-box;
28 | }
29 |
--------------------------------------------------------------------------------
/styles/theme.scss:
--------------------------------------------------------------------------------
1 | $font-default: "Inter", sans-serif;
2 | $color-accent: #a82b42;
3 | $color-text: #dfdfdf;
4 | $color-bg: #181a1b;
5 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | const sveltePreprocess = require('svelte-preprocess')
2 |
3 | module.exports = {
4 | preprocess: sveltePreprocess({
5 | scss: {
6 | includePaths: ['src']
7 | },
8 | postcss: {
9 | plugins: [require('autoprefixer'), require('cssnano')]
10 | }
11 | })
12 | }
13 |
--------------------------------------------------------------------------------