Lots of updates

This commit is contained in:
Ryahn 2025-01-31 01:42:28 -05:00
parent 1fb755dc9c
commit 58ecead96d
9 changed files with 249 additions and 70 deletions

12
ecosystem.config.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = {
apps: [{
name: "haraka-email",
script: "haraka",
args: "-c src/email_server --debug --nodejs --no-daemon",
cwd: "home/ploi/2weekmail.fyi",
watch: true,
env: {
NODE_ENV: "development",
}
}]
}

View File

@ -5,6 +5,7 @@ const knexConfig = require('../src/config/database');
const knex = Knex(knexConfig.development); const knex = Knex(knexConfig.development);
Model.knex(knex); Model.knex(knex);
const crypto = require('crypto'); const crypto = require('crypto');
const bcrypt = require('bcrypt');
const email = process.argv[2]; const email = process.argv[2];
let password = process.argv[3]; let password = process.argv[3];
@ -30,7 +31,7 @@ async function main() {
password = crypto.randomBytes(16).toString('hex'); password = crypto.randomBytes(16).toString('hex');
} }
user.password = password; user.password = await bcrypt.hash(password, 10);
await user.$query().patch(); await user.$query().patch();
console.log(`User password updated: ${user.email}`); console.log(`User password updated: ${user.email}`);

View File

@ -0,0 +1,25 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable("stats", (table) => {
table.increments('id').primary();
table.date('date').notNullable();
table.integer('email_count').notNullable().defaultTo(0);
table.integer('message_count').notNullable().defaultTo(0);
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
table.unique('date');
table.index('date');
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('stats');
};

View File

@ -0,0 +1,93 @@
const BaseModel = require('./Base');
const { mysqlSafeTimestamp } = require('../../utils/functions');
class DailyStats extends BaseModel {
static get tableName() {
return 'stats';
}
static get jsonSchema() {
return {
type: 'object',
required: ['date', 'email_count', 'message_count'],
properties: {
id: { type: 'integer' },
date: { type: 'string', format: 'date' },
email_count: { type: 'integer', minimum: 0 },
message_count: { type: 'integer', minimum: 0 },
created_at: { type: 'string', format: 'date-time' },
updated_at: { type: 'string', format: 'date-time' }
}
};
}
static async incrementEmailCount() {
const today = mysqlSafeTimestamp(true);
return this.query()
.insert({
date: today,
email_count: 1,
message_count: 0
})
.onConflict('date')
.merge({
email_count: this.knex().raw('stats.email_count + 1'),
updated_at: mysqlSafeTimestamp()
});
}
static async incrementMessageCount() {
const today = mysqlSafeTimestamp(true);
return this.query()
.insert({
date: today,
email_count: 0,
message_count: 1
})
.onConflict('date')
.merge({
message_count: this.knex().raw('stats.message_count + 1'),
updated_at: mysqlSafeTimestamp()
});
}
static async getTotalStats() {
const result = await this.query()
.select(
this.knex().raw('SUM(email_count) as total_emails'),
this.knex().raw('SUM(message_count) as total_messages')
)
.first();
return {
emailsCreated: parseInt(result.total_emails) || 0,
messagesReceived: parseInt(result.total_messages) || 0
};
}
static async getWeeklyStats() {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - 6); // Last 7 days including today
const stats = await this.query()
.select('date', 'email_count', 'message_count')
.whereBetween('date', [
startDate.toISOString().split('T')[0],
endDate.toISOString().split('T')[0]
])
.orderBy('date');
// Convert to Recharts format
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return stats.map(stat => ({
name: days[new Date(stat.date).getDay()],
emails: stat.email_count,
messages: stat.message_count
}));
}
}
module.exports = DailyStats;

View File

@ -9,10 +9,12 @@ exports.seed = async function(knex) {
{ name: 'icantreadpls.fyi', active: true}, { name: 'icantreadpls.fyi', active: true},
{ name: '20is20butimnotgay.top', active: true}, { name: '20is20butimnotgay.top', active: true},
{ name: '20is20butimnotgay.fyi', active: true}, { name: '20is20butimnotgay.fyi', active: true},
{ name: '20is20butimnotgay.com', active: true},
{ name: 'bigwhitevanfbi.top', active: true}, { name: 'bigwhitevanfbi.top', active: true},
{ name: 'bigwhitevanfbi.fyi', active: true}, { name: 'bigwhitevanfbi.fyi', active: true},
{ name: 'idonthaveabig.wang,wang', active: true}, { name: 'bigwhitevanfbi.com', active: true},
{ name: '2weekmail.fyi', active: true}, { name: 'idonthaveabig.wang', active: true},
{ name: '2weekmail.test', active: true} { name: 'idonthaveabigwang.com', active: true},
{ name: '2weekmail.fyi', active: true}
]); ]);
}; };

View File

@ -1,6 +1,8 @@
exports.register = function () { exports.register = function () {
const plugin = this; const plugin = this;
const MessageService = require("../../services/MessageService"); const MessageService = require("../../services/MessageService");
const path = require('path');
const DailyStats = require(path.join(__dirname, '../../../db/models/DailyStats'));
plugin.store_message = async function (next, connection) { plugin.store_message = async function (next, connection) {
const transaction = connection.transaction; const transaction = connection.transaction;
@ -31,6 +33,7 @@ exports.register = function () {
}; };
await MessageService.store(messageData); await MessageService.store(messageData);
await DailyStats.incrementMessageCount();
next(); next();
} catch (error) { } catch (error) {
connection.logerror(plugin, `Failed to store message: ${error.message}`); connection.logerror(plugin, `Failed to store message: ${error.message}`);

View File

@ -4,34 +4,8 @@ const { authenticateToken } = require('../middleware/auth');
const TempEmail = require('../db/models/TempEmail'); const TempEmail = require('../db/models/TempEmail');
router.use(authenticateToken); router.use(authenticateToken);
const Domain = require('../db/models/Domain'); const Domain = require('../db/models/Domain');
const { uniqueNamesGenerator, adjectives, animals, colors, names, languages, starWars, countries } = require('unique-names-generator'); const DailyStats = require('../db/models/DailyStats');
const { generateUniqueName, getRandomDomain, mysqlSafeTimestamp } = require('../utils/functions');
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}`;
}
/** /**
* @swagger * @swagger
@ -78,6 +52,10 @@ function generateUniqueName() {
* type: string * type: string
* description: Custom name for the temporary email (optional) * description: Custom name for the temporary email (optional)
* example: john-doe * example: john-doe
* safeTLD:
* type: boolean
* description: Whether to return .com domains. Default is false.
* example: false
* responses: * responses:
* 200: * 200:
* description: Temporary email successfully generated * description: Temporary email successfully generated
@ -164,34 +142,36 @@ function generateUniqueName() {
*/ */
router.post('/generate', async (req, res) => { router.post('/generate', async (req, res) => {
try { try {
const randomDomain = await Domain.query() const safeTLD = req.body.safeTLD || false;
.where('active', true) const randomDomain = await getRandomDomain(safeTLD);
.orderByRaw('RAND()')
.first(); if (randomDomain.status === 'error') {
res.status(500).json({ status: 'error', message: randomDomain.message });
}
const emailName = req.body.name || generateUniqueName(); const emailName = req.body.name || generateUniqueName();
const randomDomainName = `${emailName}@${randomDomain.name}`; const randomDomainName = `${emailName}@${randomDomain.domain.name}`;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() + 14);
const tempEmail = await TempEmail.query().insert({ const tempEmail = await TempEmail.query().insert({
email: randomDomainName, email: randomDomainName,
user_id: req.user.id, user_id: req.user.id,
expires_at: cutoffDate expires_at: mysqlSafeTimestamp(14)
}); });
await DailyStats.incrementEmailCount();
res.json(tempEmail); res.json({ status: 'success', email: { id: tempEmail.id, address: randomDomainName } });
} catch (error) { } catch (error) {
console.error('Error creating temp email:', error); console.error('Error creating temp email:', error);
res.status(500).json({ error: error.message }); res.status(500).json({ status: 'error', message: error.message });
} }
}); });
router.get('/list', async (req, res) => { router.get('/list', async (req, res) => {
const tempEmails = await TempEmail.query().where('user_id', req.user.id); const tempEmails = await TempEmail.query().where('user_id', req.user.id);
res.json(tempEmails); res.json({ status: 'success', emails: tempEmails });
}); });
router.delete('/delete/:id', async (req, res) => { router.delete('/delete/:id', async (req, res) => {

74
src/utils/functions.js Normal file
View File

@ -0,0 +1,74 @@
const { uniqueNamesGenerator, adjectives, animals, colors, names, languages, starWars, countries } = require('unique-names-generator');
const Domain = require('../db/models/Domain');
/**
* @param {number} days - Number of days to add to the current date
* @returns {string} - MySQL safe timestamp
*/
function mysqlSafeTimestamp(dateOnly = false, days) {
if (dateOnly && days) {
return new Date(new Date().setDate(new Date().getDate() + days)).toISOString().split('T')[0];
}
if (days) {
return new Date(new Date().setDate(new Date().getDate() + days)).toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
}
if (dateOnly) {
return new Date().toISOString().split('T')[0];
}
return new Date().toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
}
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}`;
}
async function getRandomDomain(safeTLD = false) {
let query = Domain.query().where('active', true);
if (safeTLD) {
query = query.where('name', 'like', '%.com');
}
const domains = await query;
if (domains.length === 0) {
return { status: 'error', message: 'No matching domains found' };
}
const randomDomain = domains[Math.floor(Math.random() * domains.length)];
return { status: 'success', domain: { id: randomDomain.id, name: randomDomain.name } };
}
module.exports = {
mysqlSafeTimestamp,
generateUniqueName,
getRandomDomain
}

41
test.js
View File

@ -1,30 +1,19 @@
const { uniqueNamesGenerator, adjectives, animals, colors, names, languages, starWars, countries } = require('unique-names-generator');
function generateUniqueName() { const { Model } = require('objection');
const dictionaries = [adjectives, animals, colors, names, languages, starWars, countries]; const Knex = require('knex');
const knexConfig = require('./src/config/database');
const randomIndex1 = Math.floor(Math.random() * dictionaries.length); const knex = Knex(knexConfig.development);
let randomIndex2 = Math.floor(Math.random() * dictionaries.length); Model.knex(knex);
const { getRandomDomain, mysqlSafeTimestamp } = require('./src/utils/functions');
while (randomIndex2 === randomIndex1) { const DailyStats = require('./src/db/models/DailyStats');
randomIndex2 = Math.floor(Math.random() * dictionaries.length);
}
const randomName = uniqueNamesGenerator({ async function main() {
dictionaries: [dictionaries[randomIndex1], dictionaries[randomIndex2]], // const domain = await getRandomDomain(true);
separator: '-', // console.log(domain);
style: 'lowerCase' // console.log(mysqlSafeTimestamp(true, 14));
}); const stats = await DailyStats.getTotalStats();
console.log(stats);
const formattedName = randomName process.exit(0);
.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()); main();