tududi/backend/modules/projects/repository.js
Chris 542be2c1e9
Fix bug 366 (#764)
* Optimize DB

* Clean up names

* fixup! Clean up names

* fixup! fixup! Clean up names
2026-01-07 18:18:07 +02:00

256 lines
7 KiB
JavaScript

'use strict';
const BaseRepository = require('../../shared/database/BaseRepository');
const {
Project,
Task,
Tag,
Area,
Note,
User,
Permission,
sequelize,
} = require('../../models');
const { Op } = require('sequelize');
class ProjectsRepository extends BaseRepository {
constructor() {
super(Project);
}
/**
* Find all projects with filters and includes.
*/
async findAllWithFilters(whereClause) {
return this.model.findAll({
where: whereClause,
include: [
{
model: Task,
required: false,
attributes: ['id', 'status'],
where: {
parent_task_id: null,
recurring_parent_id: null,
},
},
{
model: Area,
required: false,
attributes: ['id', 'uid', 'name'],
},
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
},
{
model: User,
required: false,
attributes: ['uid'],
},
],
order: [['name', 'ASC']],
});
}
/**
* Get share counts for multiple projects.
*/
async getShareCounts(projectUids) {
if (projectUids.length === 0) return {};
const shareCounts = await Permission.findAll({
attributes: [
'resource_uid',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
],
where: {
resource_type: 'project',
resource_uid: { [Op.in]: projectUids },
},
group: ['resource_uid'],
raw: true,
});
const uidToCount = {};
shareCounts.forEach((item) => {
uidToCount[item.resource_uid] = parseInt(item.count, 10);
});
return uidToCount;
}
/**
* Find project by UID (simple).
*/
async findByUid(uid) {
return this.model.findOne({
where: { uid },
attributes: ['id', 'uid', 'user_id'],
});
}
/**
* Find project by UID with full includes.
*/
async findByUidWithIncludes(uid) {
return this.model.findOne({
where: { uid },
include: [
{
model: Task,
required: false,
where: {
parent_task_id: null,
recurring_parent_id: null,
},
include: [
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
required: false,
},
{
model: Task,
as: 'Subtasks',
include: [
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
required: false,
},
],
required: false,
},
],
},
{
model: Note,
required: false,
attributes: [
'id',
'uid',
'title',
'content',
'created_at',
'updated_at',
],
include: [
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
},
],
},
{
model: Area,
required: false,
attributes: ['id', 'uid', 'name'],
},
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
},
],
});
}
/**
* Find project by UID with tags and area.
*/
async findByUidWithTagsAndArea(uid) {
return this.model.findOne({
where: { uid },
include: [
{
model: Tag,
attributes: ['id', 'name', 'uid'],
through: { attributes: [] },
},
{
model: Area,
required: false,
attributes: ['id', 'uid', 'name'],
},
],
});
}
/**
* Get share count for a single project.
*/
async getShareCount(projectUid) {
return Permission.count({
where: {
resource_type: 'project',
resource_uid: projectUid,
},
});
}
/**
* Find area by UID.
*/
async findAreaByUid(uid) {
return Area.findOne({
where: { uid },
attributes: ['id'],
});
}
/**
* Delete project with orphaning tasks and notes.
*/
async deleteWithOrphaning(project, userId) {
await sequelize.transaction(async (transaction) => {
await sequelize.query('PRAGMA foreign_keys = OFF', { transaction });
try {
await Task.update(
{ project_id: null },
{
where: { project_id: project.id, user_id: userId },
transaction,
}
);
await Note.update(
{ project_id: null },
{
where: { project_id: project.id, user_id: userId },
transaction,
}
);
await project.destroy({ transaction });
} finally {
await sequelize.query('PRAGMA foreign_keys = ON', {
transaction,
});
}
});
}
/**
* Find existing tags by names for a user.
*/
async findTagsByNames(userId, tagNames) {
return Tag.findAll({
where: { user_id: userId, name: tagNames },
});
}
/**
* Create a tag.
*/
async createTag(name, userId) {
return Tag.create({ name, user_id: userId });
}
}
module.exports = new ProjectsRepository();