Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions web_server/db/models/Counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const mongoose = require('mongoose');

const counterSchema = new mongoose.Schema(
{
_id: { type: String, required: true },
seq: { type: Number, default: 0 },
},
{ collection: 'counters' }
);

module.exports = mongoose.models.Counter || mongoose.model('Counter', counterSchema);


21 changes: 21 additions & 0 deletions web_server/db/models/Label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const mongoose = require('mongoose');

const labelSchema = new mongoose.Schema(
{
id: { type: Number, unique: true, index: true },
name: { type: String, required: true, trim: true },
owner: { type: Number, required: true },
parent: { type: Number, default: null },
},
{
timestamps: true,
collection: 'labels',
}
);

// Prevent duplicate names for the same owner and parent
labelSchema.index({ owner: 1, name: 1, parent: 1 }, { unique: true });

module.exports = mongoose.models.Label || mongoose.model('Label', labelSchema);


39 changes: 39 additions & 0 deletions web_server/db/models/Mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const mongoose = require('mongoose');

const fileSchema = new mongoose.Schema(
{
name: String,
url: String,
size: Number,
type: String,
},
{ _id: false }
);

const mailSchema = new mongoose.Schema(
{
id: { type: Number, unique: true, index: true },
owner: { type: Number, required: true },
from: { type: Number, required: true },
sentTo: { type: [Number], default: [] },
subject: { type: String, default: '' },
body: { type: String, default: '' },
labels: { type: [Number], default: [] },
isRead: { type: Boolean, default: false },
isStarred: { type: Boolean, default: false },
isTrashed: { type: Boolean, default: false },
isSpam: { type: Boolean, default: false },
isDraft: { type: Boolean, default: false },
files: { type: [fileSchema], default: [] },
},
{
timestamps: true,
collection: 'mails',
}
);

mailSchema.index({ owner: 1, isDraft: 1, createdAt: -1 });

module.exports = mongoose.models.Mail || mongoose.model('Mail', mailSchema);


26 changes: 26 additions & 0 deletions web_server/db/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema(
{
id: { type: Number, unique: true, index: true },
fullName: { type: String, required: true },
mail: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
},
password: { type: String, required: true },
dateOfBirth: { type: String },
image: { type: String },
},
{
timestamps: true,
collection: 'users',
}
);

module.exports = mongoose.models.User || mongoose.model('User', userSchema);


166 changes: 55 additions & 111 deletions web_server/models/labels.js
Original file line number Diff line number Diff line change
@@ -1,136 +1,80 @@
// Initial example labels
let labels = [
{ id: 1, name: 'work', owner: 1, parent: null },
{ id: 2, name: 'friends', owner: 1, parent: null },
{ id: 3, name: 'work', owner: 2, parent: null },
{ id: 4, name: 'work', owner: 3, parent: null }
];
const Label = require('../db/models/Label');
const Counter = require('../db/models/Counter');

// Simple incrementing ID
let nextId = labels.length + 1;
async function nextSequence(sequenceName) {
const updated = await Counter.findByIdAndUpdate(
sequenceName,
{ $inc: { seq: 1 } },
{ new: true, upsert: true }
);
return updated.seq;
}

/**
* Return all labels owned by a specific user.
* @param {number} userId - ID of the user requesting the labels
* @returns {Array} Filtered label objects owned by the user
*/
function getAllLabels(userId) {
return labels.filter(l => l.owner == userId);
async function getAllLabels(userId) {
return Label.find({ owner: userId }).lean();
}

/**
* Find a label by its ID and owner
* @param {number} id - ID of label
* @param {number} owner - ID of the user
* @returns {object|null} Label object or null if not found
*/
function getLabelById(id,owner) {
return labels.find(l => l.id == id && l.owner == owner) || null;
async function getLabelById(id, owner) {
return Label.findOne({ id, owner }).lean();
}

/**
* Create a new top-level label (parent is null)
* @param {number} owner - User ID
* @param {string} name - Label name
* @returns {object} The created label
*/
function createNewLabel(owner, name) {
if (isDuplicateLabel(owner, name, null)) return null;
const lab = { id: nextId++, owner, name, parent: null };
labels.push(lab);
return lab;
async function createNewLabel(owner, name) {
// unique index on {owner,name,parent:null}
const id = await nextSequence('labels');
try {
const created = await Label.create({ id, owner, name, parent: null });
return created.toObject();
} catch (err) {
if (err && err.code === 11000) return null; // duplicate
throw err;
}
}

/**
* Check if a label with the same name already exists
* for a given user and (optional) parent.
* Used to prevent duplicates on create/edit.
* @param {number} owner - User ID
* @param {string} name - Label name to check
* @param {number|null} parent - Parent label ID or null
* @param {number|null} excludeId - Optional: label ID to exclude from check (for editing)
* @returns {boolean} True if duplicate exists, false otherwise
*/
function isDuplicateLabel(owner, name, parent = null, excludeId = null) {
return labels.some(l =>
l.owner === owner &&
l.name === name &&
l.parent === parent &&
(excludeId === null || l.id !== excludeId)
);
async function isDuplicateLabel(owner, name, parent = null, excludeId = null) {
const query = { owner, name, parent };
if (excludeId !== null) {
query.id = { $ne: excludeId };
}
const existing = await Label.findOne(query).lean();
return !!existing;
}

/**
* Create a sublabel under an existing label
* @param {number} owner - User ID
* @param {number} parent - Parent label ID
* @param {string} name - Sublabel name
* @returns {object|null} New label, or null if parent not found
*/
function createSublabel(owner, parent, name) {
// Check parent exists (could also check ownership)
const parentLabel = getLabelById(parent, owner);
async function createSublabel(owner, parent, name) {
const parentLabel = await Label.findOne({ id: parent, owner }).lean();
if (!parentLabel) return null;

// Prevent duplicate sublabel names under the same parent for the same user
if (isDuplicateLabel(owner, name, parent)) throw new Error("409")

const newLabel = {
id: nextId++,
name,
owner,
parent,
};
labels.push(newLabel);
return newLabel;
if (await isDuplicateLabel(owner, name, parent)) throw new Error('409');
const id = await nextSequence('labels');
const created = await Label.create({ id, owner, name, parent });
return created.toObject();
}

/**
* Rename a label by ID
* @param {number} id
* @param {string} newName
* @param {number} owner - User ID
* @returns {object|null} Updated label or null if not found
*/
function editLabelById(id, owner,newName) {
// Check parent exists (could also check ownership)
const Label = getLabelById(id, owner);
if (!Label) return null;
// Check if another label with the same name already exists
if (isDuplicateLabel(owner, newName, Label.parent, id)) {
throw new Error("409");
}
// Rename the label
Label.name = newName;
return Label;
async function editLabelById(id, owner, newName) {
const existing = await Label.findOne({ id, owner });
if (!existing) return null;
if (await isDuplicateLabel(owner, newName, existing.parent, id)) throw new Error('409');
existing.name = newName;
await existing.save();
return existing.toObject();
}

/**
* Delete a label by ID (does not cascade to sublabels)
* @param {number} id
* @param {number} owner - User ID
* @returns {boolean} True if deleted, false if not found
*/
function deleteLabelById(id,owner) {
const idx = labels.findIndex(l => l.id == id && l.owner == owner);
if (idx < 0) return false;
// Recursively delete children
const childLabels = labels.filter(l => l.parent == id && l.owner == owner);
childLabels.forEach(child => {
deleteLabelById(child.id, owner);
});

// Delete the label itself
labels.splice(idx, 1);
async function deleteLabelById(id, owner) {
const existing = await Label.findOne({ id, owner }).lean();
if (!existing) return false;
// delete children recursively
const children = await Label.find({ parent: id, owner }).lean();
for (const child of children) {
// eslint-disable-next-line no-await-in-loop
await deleteLabelById(child.id, owner);
}
await Label.deleteOne({ id, owner });
return true;
}

// Export all functions
module.exports = {
getAllLabels,
getLabelById,
createNewLabel,
createSublabel,
editLabelById,
deleteLabelById
deleteLabelById,
};
Loading