diff --git a/backend/code/.gitignore b/backend/code/.gitignore index e47273e..0e4fb52 100644 --- a/backend/code/.gitignore +++ b/backend/code/.gitignore @@ -32,4 +32,6 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +users.txt diff --git a/backend/code/accountPickerCli.ts b/backend/code/accountPickerCli.ts new file mode 100644 index 0000000..ad90f7f --- /dev/null +++ b/backend/code/accountPickerCli.ts @@ -0,0 +1,74 @@ +import * as figlet from 'figlet'; +import * as fs from 'fs'; +import * as readline from 'node:readline'; +(async () => { + const header = figlet.textSync('Account Picker', { + font: 'Roman', + horizontalLayout: 'default', + verticalLayout: 'default', + }); + + console.log(header); + + const users = fs + .readFileSync('./users.txt', 'utf8') + .trim() + .split('\n') + .map((user) => { + const [email, password] = user.split(':'); + return { email, password }; + }); + + // list users to choose from + const userChoices = users.map((user, index) => { + return { + name: `${user.email} (${user.password})`, + value: index, + }; + }); + + // list uset userChoices + // + for (const userChoice of userChoices) { + console.log(`${userChoice.value}: ${userChoice.name}`); + } + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const question = (query: string) => + new Promise((resolve) => + rl.question(query, (ans) => { + resolve(ans); + }), + ); + + const userIndex = await question('User index: '); + const user = users[parseInt(userIndex as string, 10)]; + console.log(`You chose: ${user.email} (${user.password})`); + rl.close(); + + const codeTemplate = ` +var myHeaders = new Headers(); +myHeaders.append("Content-Type", "application/json"); + +var raw = JSON.stringify({ + "email": "${user.email}", + "password": "${user.password}" +}); + +var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' +}; + +fetch("http://localhost:3001/auth/login", requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error));`; + console.log(codeTemplate); +})(); diff --git a/backend/code/package-lock.json b/backend/code/package-lock.json index 90bc4b7..9234f8e 100644 --- a/backend/code/package-lock.json +++ b/backend/code/package-lock.json @@ -1,11 +1,11 @@ { - "name": "hands_ondirt", + "name": "PongGame", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hands_ondirt", + "name": "PongGame", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { @@ -43,25 +43,34 @@ "unique-username-generator": "^1.2.0" }, "devDependencies": { + "@faker-js/faker": "^8.2.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/bcrypt": "^5.0.0", "@types/cookie-parser": "^1.4.4", "@types/express": "^4.17.17", + "@types/figlet": "^1.5.7", "@types/jest": "^29.5.2", "@types/multer": "^1.4.8", "@types/node": "^20.3.1", "@types/passport-jwt": "^3.0.9", + "@types/prompt": "^1.1.7", + "@types/prompt-sync": "^4.2.2", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "clipboardy": "^4.0.0", + "copy-to-clipboard": "^3.3.3", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", + "figlet": "^1.7.0", "jest": "^29.5.0", "prettier": "^3.0.0", "prisma-dbml-generator": "^0.10.0", + "prompt": "^1.3.0", + "prompt-sync": "^4.2.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", @@ -862,7 +871,6 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, - "optional": true, "engines": { "node": ">=0.1.90" } @@ -967,6 +975,22 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz", + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -1668,6 +1692,15 @@ } } }, + "node_modules/@nestjs/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@nestjs/cli/node_modules/typescript": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", @@ -2784,6 +2817,12 @@ "@types/send": "*" } }, + "node_modules/@types/figlet": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@types/figlet/-/figlet-1.5.7.tgz", + "integrity": "sha512-0+XwDLeH346mAl3fmw/AaEctBrhkcJl0wZezoNUQ5Tg6J5YW7xL2v9hH3Yg5L4dk/sogEdQexTrNncqjGKwoVw==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -2915,6 +2954,22 @@ "@types/passport": "*" } }, + "node_modules/@types/prompt": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@types/prompt/-/prompt-1.1.7.tgz", + "integrity": "sha512-RNOOfuVLljqRTeYpBZ12P+7mDnbEtDmsoPlOnCM+Yv2qo7+rgGZ5Q0tlqNq5OMkBKM7nGjWwlXoiTeDIjYgcdw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/revalidator": "*" + } + }, + "node_modules/@types/prompt-sync": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.2.tgz", + "integrity": "sha512-S8GBVdrchd7egtwrWtnhzZ3mFKgwlFpOCsiemzYgd5Bg75SwR5RquDlE7G4ijPbbNtAATpdn/Km3u5PEAnTsAw==", + "dev": true + }, "node_modules/@types/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.4.tgz", @@ -2941,6 +2996,12 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "node_modules/@types/revalidator": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@types/revalidator/-/revalidator-0.3.11.tgz", + "integrity": "sha512-kBfaF293K6jZJy8L3PHnxzf05G219vzifhgzbZP4P5NuDMx5HvBu2NJlNvnGD8GXI8ojzY/oypmZE55Q8uS0Ew==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", @@ -4430,6 +4491,172 @@ "node": ">= 10" } }, + "node_modules/clipboardy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", + "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", + "dev": true, + "dependencies": { + "execa": "^8.0.1", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/clipboardy/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/clipboardy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/clipboardy/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4540,6 +4767,15 @@ "color-support": "bin.js" } }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4552,15 +4788,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/comment-json": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", @@ -4711,6 +4938,15 @@ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dev": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js": { "version": "3.32.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.2.tgz", @@ -4841,6 +5077,15 @@ "node": ">=8" } }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5828,6 +6073,15 @@ "node": ">=4" } }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5897,6 +6151,18 @@ "bser": "2.1.1" } }, + "node_modules/figlet": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.7.0.tgz", + "integrity": "sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==", + "dev": true, + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -6978,6 +7244,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -6989,6 +7270,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -9039,6 +9326,58 @@ "node": ">=0.4.0" } }, + "node_modules/prompt": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "async": "3.2.3", + "read": "1.0.x", + "revalidator": "0.1.x", + "winston": "2.x" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/prompt-sync": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", + "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", + "dev": true, + "dependencies": { + "strip-ansi": "^5.0.0" + } + }, + "node_modules/prompt-sync/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prompt-sync/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prompt/node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -9300,6 +9639,18 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -9630,6 +9981,15 @@ "node": ">=0.10.0" } }, + "node_modules/revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/rimraf": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", @@ -10110,6 +10470,15 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -10356,6 +10725,18 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -10722,6 +11103,12 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "dev": true + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -11388,6 +11775,32 @@ "node": ">=8.12.0" } }, + "node_modules/winston": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", + "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/backend/code/package.json b/backend/code/package.json index 04d574c..f131466 100644 --- a/backend/code/package.json +++ b/backend/code/package.json @@ -59,25 +59,34 @@ "unique-username-generator": "^1.2.0" }, "devDependencies": { + "@faker-js/faker": "^8.2.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/bcrypt": "^5.0.0", "@types/cookie-parser": "^1.4.4", "@types/express": "^4.17.17", + "@types/figlet": "^1.5.7", "@types/jest": "^29.5.2", "@types/multer": "^1.4.8", "@types/node": "^20.3.1", "@types/passport-jwt": "^3.0.9", + "@types/prompt": "^1.1.7", + "@types/prompt-sync": "^4.2.2", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "clipboardy": "^4.0.0", + "copy-to-clipboard": "^3.3.3", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", + "figlet": "^1.7.0", "jest": "^29.5.0", "prettier": "^3.0.0", "prisma-dbml-generator": "^0.10.0", + "prompt": "^1.3.0", + "prompt-sync": "^4.2.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", diff --git a/backend/code/prisma/dbml/schema.dbml b/backend/code/prisma/dbml/schema.dbml index 58f63be..3681745 100644 --- a/backend/code/prisma/dbml/schema.dbml +++ b/backend/code/prisma/dbml/schema.dbml @@ -11,6 +11,7 @@ Table users { refreshedHash String intraId String [unique] profileFinished Boolean [not null, default: false] + online Boolean [not null, default: false] Username String [unique] firstName String lastName String @@ -133,24 +134,24 @@ Enum RoomType { dm } -Ref: friends.fromId > users.userId +Ref: friends.fromId > users.userId [delete: Cascade] -Ref: friends.toId > users.userId +Ref: friends.toId > users.userId [delete: Cascade] -Ref: blocked_friends.blocked_by_id > users.userId +Ref: blocked_friends.blocked_by_id > users.userId [delete: Cascade] -Ref: blocked_friends.blocked_id > users.userId +Ref: blocked_friends.blocked_id > users.userId [delete: Cascade] -Ref: matches.participant1Id > users.userId +Ref: matches.participant1Id > users.userId [delete: Cascade] -Ref: matches.participant2Id > users.userId +Ref: matches.participant2Id > users.userId [delete: Cascade] Ref: messages.roomId > rooms.id [delete: Cascade] -Ref: messages.authorId > users.userId +Ref: messages.authorId > users.userId [delete: Cascade] -Ref: rooms.ownerId > users.userId +Ref: rooms.ownerId > users.userId [delete: Cascade] -Ref: room_members.userId > users.userId +Ref: room_members.userId > users.userId [delete: Cascade] Ref: room_members.roomId > rooms.id [delete: Cascade] \ No newline at end of file diff --git a/backend/code/prisma/migrations/20231105142742_add_onlien_status_to_user/migration.sql b/backend/code/prisma/migrations/20231105142742_add_onlien_status_to_user/migration.sql new file mode 100644 index 0000000..5c46879 --- /dev/null +++ b/backend/code/prisma/migrations/20231105142742_add_onlien_status_to_user/migration.sql @@ -0,0 +1,56 @@ +-- DropForeignKey +ALTER TABLE "blocked_friends" DROP CONSTRAINT "blocked_friends_blocked_by_id_fkey"; + +-- DropForeignKey +ALTER TABLE "blocked_friends" DROP CONSTRAINT "blocked_friends_blocked_id_fkey"; + +-- DropForeignKey +ALTER TABLE "friends" DROP CONSTRAINT "friends_fromId_fkey"; + +-- DropForeignKey +ALTER TABLE "friends" DROP CONSTRAINT "friends_toId_fkey"; + +-- DropForeignKey +ALTER TABLE "matches" DROP CONSTRAINT "matches_participant1Id_fkey"; + +-- DropForeignKey +ALTER TABLE "matches" DROP CONSTRAINT "matches_participant2Id_fkey"; + +-- DropForeignKey +ALTER TABLE "messages" DROP CONSTRAINT "messages_authorId_fkey"; + +-- DropForeignKey +ALTER TABLE "room_members" DROP CONSTRAINT "room_members_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "rooms" DROP CONSTRAINT "rooms_ownerId_fkey"; + +-- AlterTable +ALTER TABLE "users" ADD COLUMN "online" BOOLEAN NOT NULL DEFAULT false; + +-- AddForeignKey +ALTER TABLE "friends" ADD CONSTRAINT "friends_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "friends" ADD CONSTRAINT "friends_toId_fkey" FOREIGN KEY ("toId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "blocked_friends" ADD CONSTRAINT "blocked_friends_blocked_by_id_fkey" FOREIGN KEY ("blocked_by_id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "blocked_friends" ADD CONSTRAINT "blocked_friends_blocked_id_fkey" FOREIGN KEY ("blocked_id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "matches" ADD CONSTRAINT "matches_participant1Id_fkey" FOREIGN KEY ("participant1Id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "matches" ADD CONSTRAINT "matches_participant2Id_fkey" FOREIGN KEY ("participant2Id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "messages" ADD CONSTRAINT "messages_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "rooms" ADD CONSTRAINT "rooms_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "room_members" ADD CONSTRAINT "room_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/code/prisma/schema.prisma b/backend/code/prisma/schema.prisma index ceaf261..6e324cd 100644 --- a/backend/code/prisma/schema.prisma +++ b/backend/code/prisma/schema.prisma @@ -24,6 +24,7 @@ model User { refreshedHash String? intraId String? @unique profileFinished Boolean @default(false) + online Boolean @default(false) Username String? @unique firstName String? @@ -54,8 +55,8 @@ model Friend { fromId String toId String accepted Boolean @default(false) - from User @relation("from", fields: [fromId], references: [userId]) - to User @relation("to", fields: [toId], references: [userId]) + from User @relation("from", fields: [fromId], references: [userId], onDelete: Cascade) + to User @relation("to", fields: [toId], references: [userId], onDelete: Cascade) @@unique([fromId, toId], name: "unique_friend") @@map("friends") @@ -66,9 +67,9 @@ model BlockedUsers { createdAt DateTime @default(now()) id String @id - Blcoked_by User @relation("blocked_by", fields: [blocked_by_id], references: [userId]) + Blcoked_by User @relation("blocked_by", fields: [blocked_by_id], references: [userId], onDelete: Cascade) blocked_by_id String @unique - Blocked User @relation("blocked", fields: [blocked_id], references: [userId]) + Blocked User @relation("blocked", fields: [blocked_id], references: [userId], onDelete: Cascade) blocked_id String @unique dmRoomId String? @@ -86,8 +87,8 @@ model Match { score1 Int? score2 Int? gametype String? - participant1 User @relation("participant1", fields: [participant1Id], references: [userId]) - participant2 User @relation("participant2", fields: [participant2Id], references: [userId]) + participant1 User @relation("participant1", fields: [participant1Id], references: [userId], onDelete: Cascade) + participant2 User @relation("participant2", fields: [participant2Id], references: [userId], onDelete: Cascade) @@map("matches") } @@ -99,7 +100,7 @@ model Message { id String @id @default(cuid()) authorId String room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) - author User @relation(fields: [authorId], references: [userId]) + author User @relation(fields: [authorId], references: [userId], onDelete: Cascade) roomId String content String? @@ -116,7 +117,7 @@ model Room { ownerId String type RoomType @default(public) password String? - owner User @relation("owner", fields: [ownerId], references: [userId]) + owner User @relation("owner", fields: [ownerId], references: [userId], onDelete: Cascade) members RoomMember[] messages Message[] @@ -135,7 +136,7 @@ model RoomMember { bannedAt DateTime? is_mueted Boolean @default(false) mute_expires DateTime? - user User @relation(fields: [userId], references: [userId]) + user User @relation(fields: [userId], references: [userId], onDelete: Cascade) room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) @@unique([userId, roomId], name: "unique_user_room") diff --git a/backend/code/seeder.ts b/backend/code/seeder.ts new file mode 100644 index 0000000..a1148dd --- /dev/null +++ b/backend/code/seeder.ts @@ -0,0 +1,162 @@ +import { PrismaClient, User } from '@prisma/client'; +import { faker } from '@faker-js/faker'; +import * as bcrypt from 'bcrypt'; +import * as fs from 'fs'; + +class dataSeeder extends PrismaClient { + constructor() { + super({ + datasources: { + db: { + url: process.env.DATABASE_URL, + }, + }, + }); + } + + async seed() { + await this.$connect(); + const users = faker.helpers.multiple(this.createRandomUser, { count: 10 }); + const registeredUsers = await this.registerUser(users); + await this.makeFriendship(registeredUsers); + await this.makeMatch(registeredUsers); + await this.createRoom(registeredUsers); + await this.$disconnect(); + } + + private async registerUser(users: any): Promise { + if (fs.existsSync('users.txt')) { + console.log('users.txt exists'); + fs.unlinkSync('users.txt'); + } + const new_users: User[] = []; + for await (const user of users) { + fs.appendFile('users.txt', `${user.email}:${user.password}\n`, (err) => { + if (err) throw err; + }); + + const hash = await bcrypt.hash(user.password, 10); + + const new_user = await this.user.create({ + data: { + email: user.email, + password: hash, + Username: user.username, + firstName: user.firstName, + lastName: user.lastName, + avatar: user.avatar, + discreption: user.bio, + profileFinished: true, + }, + }); + new_users.push(new_user); + } + return new_users; + } + + private async makeFriendship(users: User[]) { + for await (const user of users) { + const randomUser = users[Math.floor(Math.random() * users.length)]; + if (randomUser.userId !== user.userId) { + const friendshipid = [user.userId, randomUser.userId].sort().join('-'); + await this.friend.upsert({ + where: { + id: friendshipid, + }, + create: { + id: friendshipid, + from: { + connect: { + userId: user.userId, + }, + }, + to: { + connect: { + userId: randomUser.userId, + }, + }, + accepted: true, + }, + update: {}, + }); + } + } + } + + private async makeMatch(users: User[]) { + for await (const user of users) { + const randomUser = users[Math.floor(Math.random() * users.length)]; + if (randomUser.userId !== user.userId) { + await this.match.create({ + data: { + participant1Id: user.userId, + participant2Id: randomUser.userId, + winner_id: Math.random() > 0.5 ? user.userId : randomUser.userId, + score1: Math.floor(Math.random() * 10), + score2: Math.floor(Math.random() * 10), + }, + }); + } + } + } + + private createRandomUser() { + return { + username: faker.internet.userName(), + email: faker.internet.email(), + avatar: 'v1698656518/nest-blog/clocnzgx80006q73seyj9hzx5.png', + password: faker.internet.password(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + bio: faker.person.bio(), + }; + } + + private async createRoom(users: User[]) { + const owner = users[Math.floor(Math.random() * users.length)]; + const roomData = { + name: faker.lorem.word(), + ownerId: owner.userId, + }; + + // add random users to the room + const filtredUsers = users.filter((user) => user.userId !== owner.userId); + const randomUsers = filtredUsers.slice( + 0, + Math.floor(Math.random() * filtredUsers.length), + ); + + const room = await this.room.create({ + data: { + name: roomData.name, + ownerId: roomData.ownerId, + type: 'public', + }, + }); + + for await (const user of randomUsers) { + await this.roomMember.create({ + data: { + userId: user.userId, + roomId: room.id, + is_admin: Math.random() > 0.8 ? true : false, + }, + }); + } + await this.roomMember.create({ + data: { + userId: owner.userId, + roomId: room.id, + is_admin: true, + }, + }); + + console.log("roomid: ", room.id) + console.log("ownerid: ", owner.userId) + console.log("randomUsers: ", randomUsers) + } +} + +(async () => { + await new dataSeeder().seed(); +})(); diff --git a/backend/code/src/app.controller.ts b/backend/code/src/app.controller.ts index e9983e6..e70376d 100644 --- a/backend/code/src/app.controller.ts +++ b/backend/code/src/app.controller.ts @@ -31,44 +31,214 @@ export class AppController { @Get() // @UseGuards(AuthenticatedGuard) home() { - return ` + return ` + + - - - - - - - - - + + + + + Socket.io Example + + + + +

Socket.io Example

+ +
+ + + +
    + + +
    + + + + + +
    + + + + + + +
    + + + + + +
    +
    + `; } diff --git a/backend/code/src/friends/dto/friend-profile.dto.ts b/backend/code/src/friends/dto/friend-profile.dto.ts new file mode 100644 index 0000000..059c0c3 --- /dev/null +++ b/backend/code/src/friends/dto/friend-profile.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { User } from '@prisma/client'; +import { PICTURE } from 'src/profile/dto/profile.dto'; + +export class FriendProfileDto { + constructor(friend: Partial) { + this.userId = friend?.userId; + this.firstname = friend?.firstName; + this.lastname = friend?.lastName; + this.avatar = { + thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${friend.avatar}`, + medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${friend.avatar}`, + large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${friend.avatar}`, + }; + } + + @ApiProperty({ example: 'cloh36sfy00002v6laxvhdj7r' }) + userId: string; + @ApiProperty({ example: 'John' }) + firstname: string; + @ApiProperty({ example: 'Doe' }) + lastname: string; + @ApiProperty({ + example: { + thumbnail: + 'https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/cloh36sfy00002v6laxvhdj7r', + medium: + 'https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/cloh36sfy00002v6laxvhdj7r', + large: + 'https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/cloh36sfy00002v6laxvhdj7r', + }, + }) + avatar: PICTURE; +} diff --git a/backend/code/src/friends/friends.controller.ts b/backend/code/src/friends/friends.controller.ts index d140cf9..8c47b7b 100644 --- a/backend/code/src/friends/friends.controller.ts +++ b/backend/code/src/friends/friends.controller.ts @@ -12,22 +12,10 @@ import { FriendsService } from './friends.service'; import { AtGuard } from 'src/auth/guards/at.guard'; import { FriendDto } from './dto/add-friend.dto'; import { GetCurrentUser } from 'src/auth/decorator/get_current_user.decorator'; -import { - ApiCookieAuth, - ApiProperty, - ApiQuery, - ApiResponse, - ApiTags, -} from '@nestjs/swagger'; +import { ApiCookieAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; import { FriendResponseDto } from './dto/frined-response.dto'; import { QueryOffsetDto } from './dto/query-ofsset-dto'; - -class ListType { - @ApiProperty({ example: '60f1a7b0e1b3c2a4e8b4a1a0' }) - userId: string; - firstName: string; - lastName: string; -} +import { FriendProfileDto } from './dto/friend-profile.dto'; @ApiTags('friends') @ApiCookieAuth('X-Acces-Token') @@ -107,9 +95,7 @@ export class FriendsController { @Get('list') @UseGuards(AtGuard) - @ApiQuery({ name: 'offset', required: false, type: Number }) - @ApiQuery({ name: 'limit', required: false, type: Number }) - @ApiResponse({ type: ListType }) + @ApiResponse({ type: FriendProfileDto }) async getFriendsList( @GetCurrentUser('userId') userId: string, @Query() { offset, limit }: QueryOffsetDto, @@ -119,8 +105,7 @@ export class FriendsController { @Get('requests') @UseGuards(AtGuard) - @ApiQuery({ name: 'offset', required: false, type: Number }) - @ApiQuery({ name: 'limit', required: false, type: Number }) + @ApiResponse({ type: FriendProfileDto }) async getFriendsRequests( @GetCurrentUser('userId') userId: string, @Query() { offset, limit }: QueryOffsetDto, @@ -130,6 +115,7 @@ export class FriendsController { @Get('blocklist') @UseGuards(AtGuard) + @ApiResponse({ type: FriendProfileDto }) async getBlockList( @GetCurrentUser('userId') userId: string, @Query() { offset, limit }: QueryOffsetDto, @@ -139,6 +125,7 @@ export class FriendsController { @Get('pending') @UseGuards(AtGuard) + @ApiResponse({ type: FriendProfileDto }) async getFriendsPending( @GetCurrentUser('userId') userId: string, @Query() { offset, limit }: QueryOffsetDto, diff --git a/backend/code/src/friends/friends.service.ts b/backend/code/src/friends/friends.service.ts index cf417f1..77c7349 100644 --- a/backend/code/src/friends/friends.service.ts +++ b/backend/code/src/friends/friends.service.ts @@ -3,7 +3,7 @@ import { PrismaService } from 'src/prisma/prisma.service'; import { UsersService } from 'src/users/users.service'; import { FriendResponseDto } from './dto/frined-response.dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { PICTURE } from 'src/profile/dto/profile.dto'; +import { FriendProfileDto } from './dto/friend-profile.dto'; @Injectable() export class FriendsService { @@ -225,29 +225,9 @@ export class FriendsService { return friends.map((friend) => { if (friend.from.userId === userId) { - const avatar: PICTURE = { - thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${friend.to.avatar}`, - medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${friend.to.avatar}`, - large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${friend.to.avatar}`, - }; - return { - id: friend.to.userId, - firstname: friend.to.firstName, - lastname: friend.to.lastName, - avatar, - }; + return new FriendProfileDto(friend.to); } else { - const avatar: PICTURE = { - thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${friend.from.avatar}`, - medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${friend.from.avatar}`, - large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${friend.from.avatar}`, - }; - return { - id: friend.from.userId, - firstname: friend.from.firstName, - lastname: friend.from.lastName, - avatar, - }; + return new FriendProfileDto(friend.from); } }); } @@ -266,11 +246,12 @@ export class FriendsService { userId: true, firstName: true, lastName: true, + avatar: true, }, }, }, }); - return friends.map((friend) => friend.from); + return friends.map((friend) => new FriendProfileDto(friend.from)); } async getBlockList(userId: string, offset: number, limit: number) { @@ -286,11 +267,12 @@ export class FriendsService { userId: true, firstName: true, lastName: true, + avatar: true, }, }, }, }); - return blocked.map((friend) => friend.Blocked); + return blocked.map((friend) => new FriendProfileDto(friend.Blocked)); } async getPendingRequests(userId: string, offset: number, limit: number) { @@ -307,10 +289,11 @@ export class FriendsService { userId: true, firstName: true, lastName: true, + avatar: true, }, }, }, }); - return friends.map((friend) => friend.to); + return friends.map((friend) => new FriendProfileDto(friend.to)); } } diff --git a/backend/code/src/game/game.controller.ts b/backend/code/src/game/game.controller.ts index 26dab0b..657ebf8 100644 --- a/backend/code/src/game/game.controller.ts +++ b/backend/code/src/game/game.controller.ts @@ -3,8 +3,10 @@ import { GameService } from './game.service'; import { AtGuard } from 'src/auth/guards/at.guard'; import { GetCurrentUser } from 'src/auth/decorator/get_current_user.decorator'; import { QueryOffsetDto } from 'src/friends/dto/query-ofsset-dto'; +import { ApiTags } from '@nestjs/swagger'; @Controller('game') +@ApiTags('game') export class GameController { constructor(private readonly gameService: GameService) {} diff --git a/backend/code/src/game/game.service.ts b/backend/code/src/game/game.service.ts index 06b3cc2..43fd9a2 100644 --- a/backend/code/src/game/game.service.ts +++ b/backend/code/src/game/game.service.ts @@ -1,31 +1,36 @@ import { Injectable } from '@nestjs/common'; -import { OnEvent } from '@nestjs/event-emitter'; +import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; +import { Socket } from 'socket.io'; import { PrismaService } from 'src/prisma/prisma.service'; import { PICTURE } from 'src/profile/dto/profile.dto'; @Injectable() export class GameService { - constructor(private readonly prisma: PrismaService) { + constructor( + private readonly prisma: PrismaService, + private eventEmitter: EventEmitter2, + ) { // this.launchGame(); } - private waitingPlayers: string[] = []; + private waitingPlayers: Socket[] = []; @OnEvent('game.start') - handleGameStartEvent(client: any) { - this.waitingPlayers.push(client.id); + handleGameStartEvent(client: Socket) { + this.waitingPlayers.push(client); console.log('client subscribed to the queue'); } private launchGame() { setInterval(() => { - console.log('waitingPlayers'); + console.log('waitingPlayers', this.waitingPlayers.length); if (this.waitingPlayers.length >= 2) { console.log('Game launched!'); const two_players = this.waitingPlayers.splice(0, 2); - console.log(two_players); + this.eventEmitter.emit('game.launched', two_players); + // console.log(two_players); } - }, 1000); + }, 5027); } async getHistory(userId: string, offset: number, limit: number) { diff --git a/backend/code/src/game/game.ts b/backend/code/src/game/game.ts new file mode 100644 index 0000000..983a944 --- /dev/null +++ b/backend/code/src/game/game.ts @@ -0,0 +1,57 @@ +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Socket } from 'socket.io'; + +export class Game { + constructor(private readonly eventEmitter: EventEmitter2) {} + private async loop() { + console.log('loop'); + await this.sleep(5000); + this.loop(); + } + + start(ngameid: string) { + console.log('game started', ngameid); + this.gameid = ngameid; + this.loop(); + } + + setplayerScokets(p1socket: Socket, p2socket: Socket) { + this.p1socket = p1socket; + this.p2socket = p2socket; + + this.p1socket.on('move', (data) => { + console.log('heh'); + console.log(data); + }); + this.p2socket.on('move', (data) => { + console.log('heh'); + console.log(data); + }); + this.p1socket.on('disconnect', () => { + console.log('p1 disconnected'); + this.emitGameEnd('p1 disconnected'); + }); + this.p2socket.on('disconnect', () => { + console.log('p2 disconnected'); + this.emitGameEnd('p2 disconnected'); + }); + } + + private emitGameEnd(message: string) { + console.log('game end'); + this.eventEmitter.emit('game.end', { + message: message, + gameid: this.gameid, + }); + } + + private sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + } + + private gameid: string; + private p1socket: Socket; + private p2socket: Socket; +} diff --git a/backend/code/src/gateways/gateways.gateway.ts b/backend/code/src/gateways/gateways.gateway.ts index 92e264a..e685e30 100644 --- a/backend/code/src/gateways/gateways.gateway.ts +++ b/backend/code/src/gateways/gateways.gateway.ts @@ -1,5 +1,7 @@ import { + MessageBody, OnGatewayConnection, + OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, WebSocketServer, @@ -9,18 +11,23 @@ import { MessageFormatDto } from 'src/messages/dto/message-format.dto'; import {} from '@nestjs/platform-socket.io'; import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { PrismaService } from 'src/prisma/prisma.service'; +import { Game } from 'src/game/game'; + @WebSocketGateway(3004, { cors: { origin: ['http://localhost:3001'], }, transports: ['websocket'], }) -export class Gateways implements OnGatewayConnection { +export class Gateways implements OnGatewayConnection, OnGatewayDisconnect { constructor( private prisma: PrismaService, private readonly eventEmitter: EventEmitter2, ) {} - handleConnection(client: Socket) { + + @WebSocketServer() private server: Server; + private games_map = new Map(); + async handleConnection(client: Socket) { const userId = client.data.user.sub; const rooms = this.prisma.roomMember.findMany({ where: { @@ -38,12 +45,51 @@ export class Gateways implements OnGatewayConnection { rooms.then((rooms) => { rooms.forEach((room) => { client.join(`Romm:${room.room.id}`); + console.log(`Romm:${room.room.id}`); }); }); - client.join(`notif:${userId}`); + client.join(`User:${userId}`); + + await this.prisma.user.update({ + where: { + userId, + }, + data: { + online: true, + }, + }); + + const frienduserIds = await this.prisma.friend.findMany({ + where: { + OR: [ + { + fromId: userId, + }, + { + toId: userId, + }, + ], + }, + select: { + fromId: true, + toId: true, + }, + }); + + const friendIds = frienduserIds + .map((friend) => (friend.toId === userId ? friend.fromId : friend.toId)) + .filter((id) => this.server.sockets.adapter.rooms.get(`User:${id}`)?.size); + + client.emit('onlineFriends', friendIds); + + this.server.emit('friendOnline', userId); } - @WebSocketServer() private server: Server; + async handleDisconnect(client: Socket) { + const userId = client.data.user.sub; + + this.server.emit('friendOffline', userId); + } @OnEvent('sendMessages') sendMessage(message: MessageFormatDto) { @@ -54,21 +100,95 @@ export class Gateways implements OnGatewayConnection { @OnEvent('addFriendNotif') sendFriendReq(notif: any) { - const channellname: string = `notif:${notif.recipientId}`; + const channellname: string = `User:${notif.recipientId}`; this.server.to(channellname).emit('message', notif); } @SubscribeMessage('startGame') handleGameStartEvent(client: Socket) { + console.log(client.data.user); this.eventEmitter.emit('game.start', client); } @OnEvent('game.launched') handleGameLaunchedEvent(clients: any) { const game_channel = `Game:${clients[0].id}:${clients[1].id}`; + console.log(game_channel); clients.forEach((client: any) => { client.join(game_channel); }); + const new_game = new Game(this.eventEmitter); + new_game.setplayerScokets(clients[0], clients[1]); + new_game.start(game_channel); + this.games_map.set(game_channel, new_game); this.server.to(game_channel).emit('game.launched', game_channel); } + + @OnEvent('game.end') + handleGameEndEvent(data: any) { + console.log('game ended'); + console.log(data); + this.server.to(data.gameid).emit('game.end', data); + this.games_map.delete(data.gameid); + } + + @SubscribeMessage('joinRoom') + async handleJoinRoomEvent(client: Socket, data: any) { + const member = await this.prisma.roomMember.findFirst({ + where: { + userId: client.data.user.sub, + roomId: data.roomId, + }, + }); + if (member) { + client.join(`Romm:${data.roomId}`); + } + } + + @SubscribeMessage('unban') + async handleUnbanEvent(client: Socket, data: any) { + const owner = await this.prisma.roomMember.findFirst({ + where: { + userId: client.data.user.sub, + roomId: data.roomId, + }, + }); + if (!owner || owner.is_admin === false) { + return; + } + const member = await this.prisma.roomMember.findFirst({ + where: { + userId: data.memberId, + roomId: data.roomId, + }, + }); + if (member) { + const banedClientSocket = await this.server + .in(`User:${data.memberId}`) + .fetchSockets(); + if (banedClientSocket.length > 0) { + banedClientSocket[0].join(`Romm:${data.roomId}`); + } + } + } + + @SubscribeMessage('roomDeparture') + async hundleDeparture( + @MessageBody() data: { roomId: string; memberId: string; type: string }, + ) { + const clients = await this.server.in(`Romm:${data.roomId}`).fetchSockets(); + console.log(`Romm:${data.roomId}`); + const clientToBan = clients.find( + (client) => client.data.user.sub === data.memberId, + ); + if (clientToBan) { + clientToBan.leave(`Romm:${data.roomId}`); + if (data?.type === 'kick') { + clientToBan.emit('roomDeparture', { + roomId: data.roomId, + type: 'kick', + }); + } + } + } } diff --git a/backend/code/src/main.ts b/backend/code/src/main.ts index 44be4ac..c601abb 100644 --- a/backend/code/src/main.ts +++ b/backend/code/src/main.ts @@ -45,6 +45,8 @@ async function bootstrap() { .addTag('friends') .addTag('rooms') .addTag('Messages') + .addTag('user') + .addTag('game') .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup('api', app, document); diff --git a/backend/code/src/messages/messages.controller.ts b/backend/code/src/messages/messages.controller.ts index 2a295b7..47a2a62 100644 --- a/backend/code/src/messages/messages.controller.ts +++ b/backend/code/src/messages/messages.controller.ts @@ -11,10 +11,11 @@ import { MessagesService } from './messages.service'; import { CreateMessgaeDto } from './dto/create-messgae.dto'; import { GetCurrentUser } from 'src/auth/decorator/get_current_user.decorator'; import { AtGuard } from 'src/auth/guards/at.guard'; -import { ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiCookieAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { MessageFormatDto } from './dto/message-format.dto'; import { QueryOffsetDto } from 'src/friends/dto/query-ofsset-dto'; +@ApiCookieAuth('X-Acces-Token') @ApiTags('Messages') @Controller('messages') export class MessagesController { diff --git a/backend/code/src/messages/messages.service.ts b/backend/code/src/messages/messages.service.ts index 682f041..024cfc0 100644 --- a/backend/code/src/messages/messages.service.ts +++ b/backend/code/src/messages/messages.service.ts @@ -11,6 +11,7 @@ export class MessagesService { ) {} async sendMessages(userId: string, channelId: string, messageDto: any) { + console.log(messageDto); if (messageDto.content.length > 1000) { throw new HttpException('Message is too long', HttpStatus.BAD_REQUEST); } @@ -27,7 +28,9 @@ export class MessagesService { }, }, }); - + if (!room) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } if (room.type === 'dm') { const blocked = await this.prisma.blockedUsers.findFirst({ where: { diff --git a/backend/code/src/rooms/rooms.controller.ts b/backend/code/src/rooms/rooms.controller.ts index f2801ab..a781fe2 100644 --- a/backend/code/src/rooms/rooms.controller.ts +++ b/backend/code/src/rooms/rooms.controller.ts @@ -207,4 +207,14 @@ export class RoomsController { ) { return this.roomsService.listRooms(userId, offset, limit, joined); } + + @Get('dms') + @HttpCode(HttpStatus.OK) + @UseGuards(AtGuard) + async getDMs( + @GetCurrentUser('userId') userId: string, + @Query() { offset, limit }: QueryOffsetDto, + ) { + return this.roomsService.getDMs(userId, offset, limit); + } } diff --git a/backend/code/src/rooms/rooms.service.ts b/backend/code/src/rooms/rooms.service.ts index 0413fcc..a7ce614 100644 --- a/backend/code/src/rooms/rooms.service.ts +++ b/backend/code/src/rooms/rooms.service.ts @@ -49,6 +49,22 @@ export class RoomsService { HttpStatus.FORBIDDEN, ); } + // check if already there is an existing dm room + const dmRoom = await this.prisma.room.findFirst({ + where: { + type: 'dm', + members: { + every: { + userId: { + in: [roomOwnerId, secondMember], + }, + }, + }, + }, + }); + if (dmRoom) { + return new RoomDataDto(dmRoom); + } } const room = await this.prisma.room.create({ @@ -554,6 +570,8 @@ export class RoomsService { }, select: { is_admin: true, + is_banned: true, + bannedAt: true, }, }, }), @@ -584,6 +602,9 @@ export class RoomsService { const last_message = await this.prisma.message.findFirst({ where: { roomId: room.id, + ...(room.members[0].is_banned && { + createdAt: { lte: room.members[0].bannedAt }, + }), }, orderBy: { createdAt: 'desc', @@ -607,4 +628,79 @@ export class RoomsService { ); return roomsData; } + + async getDMs(userId: string, offset: number, limit: number) { + const rooms = await this.prisma.room.findMany({ + skip: offset, + take: limit, + where: { + type: 'dm', + members: { + some: { + userId: userId, + }, + }, + }, + select: { + id: true, + type: true, + ownerId: true, + members: { + select: { + user: { + select: { + userId: true, + firstName: true, + lastName: true, + avatar: true, + }, + }, + }, + }, + }, + }); + + type DMsData = { + id: string; + name: string; + last_message: { + createdAt: Date; + content: string; + } | null; + secondMemberId: string; + avatar: PICTURE; + }; + const dmsData: DMsData[] = await Promise.all( + rooms.map(async (room) => { + const secondMember = room.members.find( + (member) => member.user.userId !== userId, + ); + const last_message = await this.prisma.message.findFirst({ + where: { + roomId: room.id, + }, + orderBy: { + createdAt: 'desc', + }, + select: { + content: true, + createdAt: true, + }, + }); + const avatar: PICTURE = { + thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${secondMember.user.avatar}`, + medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${secondMember.user.avatar}`, + large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${secondMember.user.avatar}`, + }; + return { + id: room.id, + name: secondMember.user.firstName + ' ' + secondMember.user.lastName, + secondMemberId: secondMember.user.userId, + last_message, + avatar, + }; + }), + ); + return dmsData; + } } diff --git a/backend/code/src/users/users.controller.ts b/backend/code/src/users/users.controller.ts index 929cfe5..06983a5 100644 --- a/backend/code/src/users/users.controller.ts +++ b/backend/code/src/users/users.controller.ts @@ -14,7 +14,9 @@ import { usersSearchDto } from './dto/search-user.dto'; import { TwoFactorDto } from './dto/two-factor.dto'; import { GetCurrentUser } from 'src/auth/decorator/get_current_user.decorator'; import { AuthGuard } from '@nestjs/passport'; +import { ApiTags } from '@nestjs/swagger'; +@ApiTags('user') @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {}