Lots of stuff again

This commit is contained in:
Ryahn 2025-01-27 15:59:36 -05:00
parent af06ad78c9
commit cb51c46f63
13 changed files with 337 additions and 11 deletions

35
app.js
View File

@ -0,0 +1,35 @@
const express = require('express');
const { Model } = require('objection');
const Knex = require('knex');
const cron = require('node-cron');
const knexConfig = require('./src/config/database');
const MessageService = require('./src/email_server/services/MessageService');
// Initialize Knex
const knex = Knex(knexConfig.development);
Model.knex(knex);
const app = express();
app.use(express.json());
// Routes
app.use('/auth', require('./src/routes/auth'));
app.use('/admin', require('./src/routes/admin'));
app.use('/messages', require('./src/routes/messages'));
app.use('/email', require('./src/routes/email'));
app.use('/test', require('./src/routes/test'));
// Schedule cleanup job
cron.schedule('0 0 * * *', async () => {
try {
await MessageService.cleanup();
console.log('Daily cleanup completed');
} catch (error) {
console.error('Cleanup failed:', error);
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`API server running on port ${PORT}`);
});

21
package-lock.json generated
View File

@ -14,9 +14,11 @@
"haraka": "^0.0.33",
"jsonwebtoken": "^9.0.2",
"knex": "^3.1.0",
"moniker": "^0.1.2",
"mysql2": "^3.12.0",
"node-cron": "^3.0.3",
"objection": "^3.1.5"
"objection": "^3.1.5",
"unique-names-generator": "^4.7.1"
}
},
"node_modules/@mapbox/node-pre-gyp": {
@ -1296,6 +1298,14 @@
"node": ">=10"
}
},
"node_modules/moniker": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/moniker/-/moniker-0.1.2.tgz",
"integrity": "sha512-Uj9iV0QYr6281G+o0TvqhKwHHWB2Q/qUTT4LPQ3qDGc0r8cbMuqQjRXPZuVZ+gcL7APx+iQgE8lcfWPrj1LsLA==",
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -1963,6 +1973,15 @@
"node": ">= 0.6"
}
},
"node_modules/unique-names-generator": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz",
"integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -17,8 +17,10 @@
"haraka": "^0.0.33",
"jsonwebtoken": "^9.0.2",
"knex": "^3.1.0",
"moniker": "^0.1.2",
"mysql2": "^3.12.0",
"node-cron": "^3.0.3",
"objection": "^3.1.5"
"objection": "^3.1.5",
"unique-names-generator": "^4.7.1"
}
}

View File

@ -6,12 +6,27 @@ class BaseModel extends Model {
}
$beforeInsert() {
this.created_at = new Date().toISOString();
this.updated_at = new Date().toISOString();
this.created_at = this.formatDateForMySQL(new Date());
this.updated_at = this.formatDateForMySQL(new Date());
if (this.expires_at) {
this.expires_at = this.formatDateForMySQL(new Date(this.expires_at));
}
}
$beforeUpdate() {
this.updated_at = new Date().toISOString();
this.updated_at = this.formatDateForMySQL(new Date());
}
$afterFind() {
// Format expires_at after fetching from database
if (this.expires_at) {
this.expires_at = this.formatDateForMySQL(new Date(this.expires_at));
}
}
formatDateForMySQL(date) {
return date.toISOString().slice(0, 19).replace('T', ' ');
}
}

View File

@ -4,10 +4,6 @@ class Domain extends BaseModel {
static get tableName() {
return 'domains';
}
static get relationMappings() {
return {};
}
}
module.exports = Domain;

View File

@ -7,6 +7,7 @@ class TempEmail extends BaseModel {
static get relationMappings() {
const Message = require('./Message');
const User = require('./User');
return {
messages: {
relation: BaseModel.HasManyRelation,
@ -15,6 +16,14 @@ class TempEmail extends BaseModel {
from: 'temp_emails.id',
to: 'messages.temp_email_id'
}
},
user: {
relation: BaseModel.BelongsToOneRelation,
modelClass: User,
join: {
from: 'temp_emails.user_id',
to: 'users.id'
}
}
};
}

View File

@ -25,7 +25,7 @@ exports.seed = async function(knex) {
const token = jwt.sign(
{ id: user.id, email: user.email, is_admin: user.is_admin },
config.auth.jwtSecret,
{ expiresIn: config.auth.tokenExpiry }
{ expiresIn: '20y' }
);
await knex('users').where('id', user.id).update({ api_key: token });

View File

@ -48,7 +48,9 @@ class MessageService {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - config.storage.retention);
return Message.query().where("created_at", "<", cutoffDate).delete();
const deletedMessages = await Message.query().where("created_at", "<", cutoffDate).delete();
const deletedTempEmails = await TempEmail.query().where("created_at", "<", cutoffDate).delete();
return { deletedMessages, deletedTempEmails };
}
static async search(params) {
@ -59,12 +61,19 @@ class MessageService {
if (params.temp_email) {
query = query.where("temp_email.email", params.temp_email);
}
if (params.temp_email_id) {
query = query.where("temp_email.id", params.temp_email_id);
}
if (params.from) {
query = query.where("from", "like", `%${params.from}%`);
}
if (params.to) {
query = query.where("to", "like", `%${params.to}%`);
}
if (params.subject) {
query = query.where("subject", "like", `%${params.subject}%`);
}

105
src/routes/admin.js Normal file
View File

@ -0,0 +1,105 @@
const express = require('express');
const router = express.Router();
const { authenticateToken, requireAdmin } = require('../middleware/auth');
const { Domain } = require('../db/models/Domain');
const { User } = require('../db/models/User');
const { TempEmail } = require('../db/models/TempEmail');
const MessageService = require('../email_server/services/MessageService');
router.use(authenticateToken, requireAdmin);
// Domain management
router.get('/domains', async (req, res) => {
try {
const domains = await Domain.query();
res.json(domains);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/domains', async (req, res) => {
try {
const domain = await Domain.query().insert(req.body);
res.json(domain);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.delete('/domains/:id', async (req, res) => {
try {
const deletedDomain = await Domain.query().deleteById(req.params.id);
res.json(deletedDomain);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.delete('/users/:id', async (req, res) => {
try {
const deletedUser = await User.query().deleteById(req.params.id);
res.json(deletedUser);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// User management
router.get('/users', async (req, res) => {
try {
const users = await User.query().select('id', 'email', 'is_admin', 'created_at');
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/users', async (req, res) => {
try {
const user = await User.query().insert(req.body);
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/temp-emails', async (req, res) => {
try {
const tempEmails = await TempEmail.query().withGraphFetched('user');
res.json(tempEmails);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/temp-emails', async (req, res) => {
try {
const tempEmail = await TempEmail.query().insert(req.body);
res.json(tempEmail);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.delete('/temp-emails/:id', async (req, res) => {
try {
const deletedTempEmail = await TempEmail.query().deleteById(req.params.id);
const deletedMessages = await MessageService.query().where('temp_email_id', req.params.id).delete();
res.json({ deletedTempEmail, deletedMessages });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Manual cleanup trigger
router.post('/cleanup', async (req, res) => {
try {
const deletedCount = await MessageService.cleanup();
res.json({ deletedCount });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;

72
src/routes/email.js Normal file
View File

@ -0,0 +1,72 @@
const express = require('express');
const router = express.Router();
const { authenticateToken } = require('../middleware/auth');
const TempEmail = require('../db/models/TempEmail');
router.use(authenticateToken);
const Domain = require('../db/models/Domain');
const { uniqueNamesGenerator, adjectives, animals, colors, names, languages, starWars, countries } = require('unique-names-generator');
function generateUniqueName() {
const dictionaries = [adjectives, animals, colors, names, languages, starWars, countries];
const randomIndex1 = Math.floor(Math.random() * dictionaries.length);
let randomIndex2 = Math.floor(Math.random() * dictionaries.length);
while (randomIndex2 === randomIndex1) {
randomIndex2 = Math.floor(Math.random() * dictionaries.length);
}
const randomName = uniqueNamesGenerator({
dictionaries: [dictionaries[randomIndex1], dictionaries[randomIndex2]],
separator: '-',
style: 'lowerCase'
});
const formattedName = randomName
.replace(/[^a-zA-Z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');
const randomNumber = Math.floor(Math.random() * 999999) + 1;
return `${formattedName}${randomNumber}`;
}
router.post('/', async (req, res) => {
try {
const randomDomain = await Domain.query()
.where('active', true)
.orderByRaw('RAND()')
.first();
const emailName = req.body.name || generateUniqueName();
const randomDomainName = `${emailName}@${randomDomain.name}`;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() + 14);
const tempEmail = await TempEmail.query().insert({
email: randomDomainName,
user_id: req.user.id,
expires_at: cutoffDate
});
res.json(tempEmail);
} catch (error) {
console.error('Error creating temp email:', error);
res.status(500).json({ error: error.message });
}
});
router.get('/', async (req, res) => {
const tempEmails = await TempEmail.query().where('user_id', req.user.id);
res.json(tempEmails);
});
router.delete('/:id', async (req, res) => {
const tempEmail = await TempEmail.query().where('id', req.params.id).andWhere('user_id', req.user.id).delete();
res.json(tempEmail);
});
module.exports = router;

23
src/routes/messages.js Normal file
View File

@ -0,0 +1,23 @@
const express = require('express');
const router = express.Router();
const { authenticateToken } = require('../middleware/auth');
const MessageService = require('../email_server/services/MessageService');
const { Message } = require('../db/models/Message');
router.use(authenticateToken);
router.get('/search', async (req, res) => {
try {
const messages = await MessageService.search(req.query);
res.json(messages);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/:id', async (req, res) => {
const message = await Message.query().where('id', req.params.id).withGraphFetched('temp_email').first();
res.json(message);
});
module.exports = router;

11
src/routes/test.js Normal file
View File

@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const { authenticateToken } = require('../middleware/auth');
router.use(authenticateToken);
router.get('/', (req, res) => {
res.json({ user: req.user });
});
module.exports = router;

30
test.js Normal file
View File

@ -0,0 +1,30 @@
const { uniqueNamesGenerator, adjectives, animals, colors, names, languages, starWars, countries } = require('unique-names-generator');
function generateUniqueName() {
const dictionaries = [adjectives, animals, colors, names, languages, starWars, countries];
const randomIndex1 = Math.floor(Math.random() * dictionaries.length);
let randomIndex2 = Math.floor(Math.random() * dictionaries.length);
while (randomIndex2 === randomIndex1) {
randomIndex2 = Math.floor(Math.random() * dictionaries.length);
}
const randomName = uniqueNamesGenerator({
dictionaries: [dictionaries[randomIndex1], dictionaries[randomIndex2]],
separator: '-',
style: 'lowerCase'
});
const formattedName = randomName
.replace(/[^a-zA-Z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
.replace(/^-+|-+$/g, ''); // Remove hyphens from start and end
const randomNumber = Math.floor(Math.random() * 999999) + 1;
return `${formattedName}${randomNumber}`;
}
console.log(generateUniqueName());