diff --git a/app.js b/app.js index 56a94df..8b0408d 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,34 @@ const Knex = require('knex'); const cron = require('node-cron'); const knexConfig = require('./src/config/database'); const MessageService = require('./src/email_server/services/MessageService'); +const swaggerJsdoc = require('swagger-jsdoc'); +const swaggerUi = require('swagger-ui-express'); + +const swaggerOptions = { + definition: { + openapi: '3.1.0', + info: { + title: '2weekmail API', + version: '1.0.0', + description: 'Documentation for the 2weekmail API', + }, + security: [ + { + bearerAuth: [] + } + ], + servers: [ + { + url: 'https://2weekmail.fyi', + description: 'Production server', + }, + ], + }, + apis: ['./src/routes/*.js'], +}; + +const swaggerSpec = swaggerJsdoc(swaggerOptions); + // Initialize Knex const knex = Knex(knexConfig.development); @@ -19,6 +47,8 @@ app.use('/messages', require('./src/routes/messages')); app.use('/email', require('./src/routes/email')); app.use('/test', require('./src/routes/test')); app.use('/', require('./src/routes/index')); +app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + // Schedule cleanup job cron.schedule('0 0 * * *', async () => { diff --git a/package-lock.json b/package-lock.json index 9c47017..7f35809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,61 @@ "mysql2": "^3.12.0", "node-cron": "^3.0.3", "objection": "^3.1.5", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "unique-names-generator": "^4.7.1" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -41,6 +93,19 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -157,6 +222,12 @@ "node": ">=10" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -270,6 +341,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -409,6 +486,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -507,6 +596,15 @@ "node": ">=6" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -940,6 +1038,18 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1075,6 +1185,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1087,6 +1204,13 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -1111,6 +1235,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -1490,6 +1620,13 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1910,6 +2047,92 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", + "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -2015,6 +2238,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2060,6 +2292,45 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/package.json b/package.json index 2d713e4..1862b4d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "mysql2": "^3.12.0", "node-cron": "^3.0.3", "objection": "^3.1.5", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "unique-names-generator": "^4.7.1" } } diff --git a/scripts/make_user.js b/scripts/make_user.js index c98c70d..8c8c083 100644 --- a/scripts/make_user.js +++ b/scripts/make_user.js @@ -4,24 +4,30 @@ const Knex = require('knex'); const knexConfig = require('../src/config/database'); const knex = Knex(knexConfig.development); Model.knex(knex); +const crypto = require('crypto'); const email = process.argv[2]; -const password = process.argv[3]; +let password = process.argv[3]; const isAdmin = process.argv[4] === 'true'; -if (!email || !password) { - console.error('Usage: node make_user.js [isAdmin]'); +if (!email && !password) { + console.error('Usage: node make_user.js [password] [isAdmin]'); process.exit(1); } async function main() { + if (!password) { + password = crypto.randomBytes(16).toString('hex'); + } + const user = await User.query().insert({ email: email, password: password, is_admin: isAdmin }); console.log(`User created: ${user.email}`); + console.log(`Password: ${password}`); process.exit(0); } -main(); \ No newline at end of file +main(); diff --git a/scripts/update_admin_password.js b/scripts/update_admin_password.js new file mode 100644 index 0000000..42cdf73 --- /dev/null +++ b/scripts/update_admin_password.js @@ -0,0 +1,41 @@ +const User = require('../src/db/models/User'); +const { Model } = require('objection'); +const Knex = require('knex'); +const knexConfig = require('../src/config/database'); +const knex = Knex(knexConfig.development); +Model.knex(knex); +const crypto = require('crypto'); + +const email = process.argv[2]; +let password = process.argv[3]; + +if (!email && !password) { + console.error('Usage: node update_admin_password.js [password]'); + process.exit(1); +} + +async function main() { + const user = await User.query().where('email', email).first(); + if (!user) { + console.error('User not found'); + process.exit(1); + } + + if (!user.is_admin) { + console.error('User is not an admin'); + process.exit(1); + } + + if (!password) { + password = crypto.randomBytes(16).toString('hex'); + } + + user.password = password; + + await user.$query().patch(); + console.log(`User password updated: ${user.email}`); + console.log(`New password: ${password}`); + process.exit(0); +} + +main(); \ No newline at end of file diff --git a/src/routes/auth.js b/src/routes/auth.js index 1091b54..76e7115 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,14 +1,15 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const jwt = require('jsonwebtoken'); -const User = require('../db/models/User'); -const config = require('../config/haraka'); - +const jwt = require("jsonwebtoken"); +const User = require("../db/models/User"); +const config = require("../config/haraka"); /** * @swagger * /auth/login: * post: + * tags: + * - Authentication * summary: Login and get API key * description: Login with email and password to get an API key * requestBody: @@ -17,23 +18,43 @@ const config = require('../config/haraka'); * application/json: * schema: * type: object + * required: + * - email + * - password * properties: * email: * type: string + * format: email * description: User's email * example: user@example.com * password: * type: string + * format: password * description: User's password * example: password123 + * responses: + * 200: + * description: Successful login + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * description: API key for authentication + * 401: + * description: Invalid credentials + * 422: + * description: Validation error */ -router.post('/login', async (req, res) => { +router.post("/login", async (req, res) => { try { const { email, password } = req.body; - const user = await User.query().where('email', email).first(); + const user = await User.query().where("email", email).first(); if (!user || !(await user.verifyPassword(password))) { - return res.status(401).json({ error: 'Invalid credentials' }); + return res.status(401).json({ error: "Invalid credentials" }); } const generateToken = () => { @@ -45,8 +66,8 @@ router.post('/login', async (req, res) => { }; let currentToken = await User.query() - .select('api_key') - .where('id', user.id) + .select("api_key") + .where("id", user.id) .first(); let token; @@ -57,22 +78,18 @@ router.post('/login', async (req, res) => { token = currentToken.api_key; } catch (tokenError) { token = generateToken(); - await User.query() - .where('id', user.id) - .update({ api_key: token }); + await User.query().where("id", user.id).update({ api_key: token }); } } else { token = generateToken(); - await User.query() - .where('id', user.id) - .update({ api_key: token }); + await User.query().where("id", user.id).update({ api_key: token }); } res.json({ token }); } catch (error) { - console.error('Login error:', error); + console.error("Login error:", error); res.status(500).json({ error: error.message }); } }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/src/routes/email.js b/src/routes/email.js index f1726d6..5c9e5c1 100644 --- a/src/routes/email.js +++ b/src/routes/email.js @@ -35,10 +35,38 @@ function generateUniqueName() { /** * @swagger + * components: + * schemas: + * TempEmail: + * type: object + * properties: + * id: + * type: integer + * description: The temporary email ID + * email: + * type: string + * description: The generated email address + * user_id: + * type: integer + * description: ID of the user who owns this email + * expires_at: + * type: string + * format: date-time + * description: Expiration date of the temporary email + * securitySchemes: + * bearerAuth: + * type: http + * scheme: bearer + * bearerFormat: JWT + * * /email/generate: * post: + * tags: + * - Temporary Email * summary: Generate a temporary email - * description: Generate a temporary email with a random domain + * description: Generate a temporary email with a random domain. The email will expire after 14 days. + * security: + * - bearerAuth: [] * requestBody: * required: false * content: @@ -48,7 +76,91 @@ function generateUniqueName() { * properties: * name: * type: string - * description: Name for the temporary email + * description: Custom name for the temporary email (optional) + * example: john-doe + * responses: + * 200: + * description: Temporary email successfully generated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/TempEmail' + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 500: + * description: Server error while generating email + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * + * /email/list: + * get: + * tags: + * - Temporary Email + * summary: List all temporary emails + * description: Get a list of all temporary emails for the authenticated user + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: List of temporary emails + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/TempEmail' + * 401: + * description: Unauthorized - Invalid or missing authentication token + * + * /email/delete/{id}: + * delete: + * tags: + * - Temporary Email + * summary: Delete a temporary email + * description: Delete a temporary email by its ID. Only the owner can delete their emails. + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * description: ID of the temporary email to delete + * schema: + * type: integer + * responses: + * 200: + * description: Email successfully deleted + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: success + * message: + * type: string + * example: Temp email deleted + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 404: + * description: Temporary email not found or doesn't belong to user + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: error + * message: + * type: string + * example: Temp email not found */ router.post('/generate', async (req, res) => { try { @@ -76,30 +188,12 @@ router.post('/generate', async (req, res) => { } }); -/** - * @swagger - * /email/list: - * get: - * summary: List all temporary emails - * description: Get a list of all temporary emails for the authenticated user - */ + router.get('/list', async (req, res) => { const tempEmails = await TempEmail.query().where('user_id', req.user.id); res.json(tempEmails); }); -/** - * @swagger - * /email/delete/{id}: - * delete: - * summary: Delete a temporary email - * description: Delete a temporary email by its ID - * parameters: - * - in: path - * name: id - * required: true - * description: ID of the temporary email to delete - */ router.delete('/delete/:id', async (req, res) => { const tempEmail = await TempEmail.query().where('id', req.params.id).andWhere('user_id', req.user.id).delete(); if (!tempEmail) { diff --git a/src/routes/index.js b/src/routes/index.js index 431782f..832124f 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); router.get('/', async (req, res) => { - res.json({ message: 'Hello World' }); + res.send(`

2weekmail API

API Documentation`); }); module.exports = router; \ No newline at end of file diff --git a/src/routes/messages.js b/src/routes/messages.js index a46402b..59e647b 100644 --- a/src/routes/messages.js +++ b/src/routes/messages.js @@ -7,43 +7,130 @@ router.use(authenticateToken); /** * @swagger + * components: + * schemas: + * Message: + * type: object + * properties: + * id: + * type: integer + * description: The message ID + * temp_email_id: + * type: integer + * description: ID of the temporary email that received this message + * subject: + * type: string + * description: Email subject + * sender: + * type: string + * description: Sender's email address + * content: + * type: string + * description: Message content + * created_at: + * type: string + * format: date-time + * description: Message creation timestamp + * example: + * id: 1 + * temp_email_id: 123 + * subject: "Welcome to our service" + * sender: "no-reply@example.com" + * content: "Hello, this is a test message..." + * created_at: "2024-01-27T12:00:00Z" + * * /messages/list: * post: - * summary: List messages - * description: List messages based on query parameters + * tags: + * - Messages + * summary: List messages for a temporary email + * description: Retrieve all messages received by a specific temporary email address + * security: + * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object + * required: + * - temp_email_id * properties: * temp_email_id: - * type: string + * type: integer * description: ID of the temporary email + * example: 123 + * responses: + * 200: + * description: List of messages successfully retrieved + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Message' + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 404: + * description: Temporary email not found + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * + * /messages/read/{id}: + * post: + * tags: + * - Messages + * summary: Get a specific message + * description: Retrieve a single message by its ID with associated temporary email details + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID of the message to retrieve + * example: 1 + * responses: + * 200: + * description: Message successfully retrieved + * content: + * application/json: + * schema: + * type: object + * allOf: + * - $ref: '#/components/schemas/Message' + * - type: object + * properties: + * temp_email: + * $ref: '#/components/schemas/TempEmail' + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 404: + * description: Message not found + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string */ router.post('/list', async (req, res) => { const messages = await Message.query().where('temp_email_id', req.body.temp_email_id); res.json(messages); }); -/** - * @swagger - * /messages/read/{id}: - * post: - * summary: Get a message by ID - * description: Get a message by its ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * id: - * type: string - * description: ID of the message to read - */ router.post('/read/:id', async (req, res) => { const message = await Message.query().where('id', req.body.id).withGraphFetched('temp_email').first(); res.json(message);