Add swagger API

This commit is contained in:
Ryahn 2025-01-27 20:42:47 -05:00
parent ad746af295
commit b92468f29b
9 changed files with 612 additions and 64 deletions

30
app.js
View File

@ -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 () => {

271
package-lock.json generated
View File

@ -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"
}
}
}
}

View File

@ -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"
}
}

View File

@ -4,23 +4,29 @@ 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 <email> <password> [isAdmin]');
if (!email && !password) {
console.error('Usage: node make_user.js <email> [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);
}

View File

@ -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 <email> [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();

View File

@ -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,20 +78,16 @@ 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 });
}
});

View File

@ -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) {

View File

@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router();
router.get('/', async (req, res) => {
res.json({ message: 'Hello World' });
res.send(`<h3>2weekmail API</h3><a href="/docs">API Documentation</a>`);
});
module.exports = router;

View File

@ -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);