Lots of updates
This commit is contained in:
parent
1fb755dc9c
commit
58ecead96d
12
ecosystem.config.js
Normal file
12
ecosystem.config.js
Normal 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",
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
@ -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}`);
|
||||||
|
|||||||
25
src/db/migrations/20250131051814_stats_table.js
Normal file
25
src/db/migrations/20250131051814_stats_table.js
Normal 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');
|
||||||
|
};
|
||||||
93
src/db/models/DailyStats.js
Normal file
93
src/db/models/DailyStats.js
Normal 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;
|
||||||
@ -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}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@ -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}`);
|
||||||
|
|||||||
@ -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
74
src/utils/functions.js
Normal 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
41
test.js
@ -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();
|
||||||
Loading…
x
Reference in New Issue
Block a user