feat: implementasi slug di module task
This commit is contained in:
+33
-6
@@ -151,13 +151,13 @@ async function main() {
|
|||||||
|
|
||||||
// 10. Project
|
// 10. Project
|
||||||
const proj1 = await prisma.project.create({
|
const proj1 = await prisma.project.create({
|
||||||
data: { id_admin: admin1.id, project_icon: 'code', project_name: 'Internify Platform Dev', slug: 'internify-platform-dev', description: 'Upgrading the core Internify platform backend and web client.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
data: { id_admin: admin1.id, project_icon: 'code', project_name: 'Internify Platform Dev', slug: slugify('Internify Platform Dev', 'Project 1 slug'), description: 'Upgrading the core Internify platform backend and web client.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
||||||
});
|
});
|
||||||
const proj2 = await prisma.project.create({
|
const proj2 = await prisma.project.create({
|
||||||
data: { id_admin: admin2.id, project_icon: 'shield', project_name: 'Auth Gateway Module', slug: 'auth-gateway-module', description: 'Securing API gateway with unified OAuth2 authentication.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
data: { id_admin: admin2.id, project_icon: 'shield', project_name: 'Auth Gateway Module', slug: slugify('Auth Gateway Module', 'Project 2 slug'), description: 'Securing API gateway with unified OAuth2 authentication.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
||||||
});
|
});
|
||||||
const proj3 = await prisma.project.create({
|
const proj3 = await prisma.project.create({
|
||||||
data: { id_admin: admin1.id, project_icon: 'chart', project_name: 'Recruitment Analytics Dashboard', slug: 'recruitment-analytics-dashboard', description: 'Developing analytical charts for recruitment metrics.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
data: { id_admin: admin1.id, project_icon: 'chart', project_name: 'Recruitment Analytics Dashboard', slug: slugify('Recruitment Analytics Dashboard', 'Project 3 slug'), description: 'Developing analytical charts for recruitment metrics.', start_date: new Date('2026-07-15'), end_date: new Date('2026-10-15'), status: 'active' }
|
||||||
});
|
});
|
||||||
|
|
||||||
// 11. LamaranMagang
|
// 11. LamaranMagang
|
||||||
@@ -173,13 +173,13 @@ async function main() {
|
|||||||
|
|
||||||
// 12. Task
|
// 12. Task
|
||||||
const task1 = await prisma.task.create({
|
const task1 = await prisma.task.create({
|
||||||
data: { id_project: proj1.id, title: 'Database Setup', slug: 'database-setup', description: 'Setup database schema and write initial seed script.', deadline_at: new Date('2026-08-01'), submission_type: 'url_link' }
|
data: { id_project: proj1.id, title: 'Database Setup', slug: slugify('Database Setup', 'Task 1 slug'), description: 'Setup database schema and write initial seed script.', deadline_at: new Date('2026-08-01'), submission_type: 'url_link' }
|
||||||
});
|
});
|
||||||
const task2 = await prisma.task.create({
|
const task2 = await prisma.task.create({
|
||||||
data: { id_project: proj1.id, title: 'API Specification', slug: 'api-specification', description: 'Draft comprehensive swagger API document.', deadline_at: new Date('2026-08-15'), submission_type: 'file_upload' }
|
data: { id_project: proj1.id, title: 'API Specification', slug: slugify('API Specification', 'Task 2 slug'), description: 'Draft comprehensive swagger API document.', deadline_at: new Date('2026-08-15'), submission_type: 'file_upload' }
|
||||||
});
|
});
|
||||||
const task3 = await prisma.task.create({
|
const task3 = await prisma.task.create({
|
||||||
data: { id_project: proj2.id, title: 'Implement JWT Validation', slug: 'implement-jwt-validation', description: 'Setup middleware and keys for JWT validation.', deadline_at: new Date('2026-08-10'), submission_type: 'url_link' }
|
data: { id_project: proj2.id, title: 'Implement JWT Validation', slug: slugify('Implement JWT Validation', 'Task 3 slug'), description: 'Setup middleware and keys for JWT validation.', deadline_at: new Date('2026-08-10'), submission_type: 'url_link' }
|
||||||
});
|
});
|
||||||
|
|
||||||
// 13. User
|
// 13. User
|
||||||
@@ -212,6 +212,33 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function slugify(value, fallback = 'item') {
|
||||||
|
if (!value || typeof value !== 'string') {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
let slug = value
|
||||||
|
.toString()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^a-z0-9\s-]/g, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '');
|
||||||
|
|
||||||
|
if (!slug) {
|
||||||
|
slug = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+$/.test(slug)) {
|
||||||
|
slug = `${fallback}-${slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error('Error seeding data:', e);
|
console.error('Error seeding data:', e);
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ class TaskController {
|
|||||||
async getTaskById(req, res) {
|
async getTaskById(req, res) {
|
||||||
try {
|
try {
|
||||||
const actor = getActor(req);
|
const actor = getActor(req);
|
||||||
const result = await taskService.getTaskById(req.params.id, actor);
|
const result = await taskService.getTaskById(
|
||||||
|
req.params.id,
|
||||||
|
actor,
|
||||||
|
req.query.project || req.query.id_project
|
||||||
|
);
|
||||||
|
|
||||||
if (result.err) {
|
if (result.err) {
|
||||||
return response(res, 'fail', result);
|
return response(res, 'fail', result);
|
||||||
@@ -118,7 +122,12 @@ class TaskController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actor = getActor(req);
|
const actor = getActor(req);
|
||||||
const result = await taskService.updateTask(req.params.id, validatePayload.data, actor);
|
const result = await taskService.updateTask(
|
||||||
|
req.params.id,
|
||||||
|
validatePayload.data,
|
||||||
|
actor,
|
||||||
|
req.query.project || req.query.id_project
|
||||||
|
);
|
||||||
|
|
||||||
if (result.err) {
|
if (result.err) {
|
||||||
return response(res, 'fail', result);
|
return response(res, 'fail', result);
|
||||||
@@ -139,7 +148,11 @@ class TaskController {
|
|||||||
async deleteTask(req, res) {
|
async deleteTask(req, res) {
|
||||||
try {
|
try {
|
||||||
const actor = getActor(req);
|
const actor = getActor(req);
|
||||||
const result = await taskService.deleteTask(req.params.id, actor);
|
const result = await taskService.deleteTask(
|
||||||
|
req.params.id,
|
||||||
|
actor,
|
||||||
|
req.query.project || req.query.id_project
|
||||||
|
);
|
||||||
|
|
||||||
if (result.err) {
|
if (result.err) {
|
||||||
return response(res, 'fail', result);
|
return response(res, 'fail', result);
|
||||||
@@ -178,7 +191,8 @@ class TaskController {
|
|||||||
req.params.id,
|
req.params.id,
|
||||||
validatePayload.data,
|
validatePayload.data,
|
||||||
req.file,
|
req.file,
|
||||||
actor
|
actor,
|
||||||
|
req.query.project || req.query.id_project
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.err) {
|
if (result.err) {
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ class TaskRepository {
|
|||||||
project: {
|
project: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
id_admin: true,
|
||||||
|
slug: true,
|
||||||
project_name: true,
|
project_name: true,
|
||||||
project_icon: true,
|
project_icon: true,
|
||||||
members: {
|
members: {
|
||||||
@@ -194,6 +196,100 @@ class TaskRepository {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findProjectByIdOrSlug(identifier) {
|
||||||
|
if (/^\d+$/.test(String(identifier))) {
|
||||||
|
return this.findProjectById(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return prisma.project.findUnique({
|
||||||
|
where: {
|
||||||
|
slug: identifier,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
members: {
|
||||||
|
where: {
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
id_user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findTaskBySlug(idProject, slug) {
|
||||||
|
return prisma.task.findUnique({
|
||||||
|
where: {
|
||||||
|
id_project_slug: {
|
||||||
|
id_project: parseInt(idProject),
|
||||||
|
slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
project: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
id_admin: true,
|
||||||
|
slug: true,
|
||||||
|
project_name: true,
|
||||||
|
project_icon: true,
|
||||||
|
members: {
|
||||||
|
where: {
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
full_name: true,
|
||||||
|
email: true,
|
||||||
|
professional_bio: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submissions: {
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
full_name: true,
|
||||||
|
email: true,
|
||||||
|
professional_bio: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findTaskSlugExists(idProject, slug, excludeTaskId = null) {
|
||||||
|
const where = {
|
||||||
|
id_project: parseInt(idProject),
|
||||||
|
slug,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (excludeTaskId) {
|
||||||
|
where.id = {
|
||||||
|
not: parseInt(excludeTaskId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = await prisma.task.findFirst({
|
||||||
|
where,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new TaskRepository();
|
module.exports = new TaskRepository();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
ForbiddenError,
|
ForbiddenError,
|
||||||
ConflictError,
|
ConflictError,
|
||||||
} = require('../../../helpers/error');
|
} = require('../../../helpers/error');
|
||||||
|
const { slugify } = require('../../../helpers/utils/slug');
|
||||||
|
|
||||||
class TaskService {
|
class TaskService {
|
||||||
async createTask(idProject, payload, actor = {}) {
|
async createTask(idProject, payload, actor = {}) {
|
||||||
@@ -23,12 +24,14 @@ class TaskService {
|
|||||||
return error(new ForbiddenError('Access denied: only admin or mentor can create task'));
|
return error(new ForbiddenError('Access denied: only admin or mentor can create task'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await taskRepository.findProjectById(idProject);
|
const project = await taskRepository.findProjectByIdOrSlug(idProject);
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return error(new NotFoundError('Project not found'));
|
return error(new NotFoundError('Project not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskSlug = await this.generateUniqueTaskSlug(project.id, payload.title);
|
||||||
|
|
||||||
const accessError = await this.validateProjectAccess(idProject, actor);
|
const accessError = await this.validateProjectAccess(idProject, actor);
|
||||||
|
|
||||||
if (accessError) {
|
if (accessError) {
|
||||||
@@ -39,6 +42,7 @@ class TaskService {
|
|||||||
|
|
||||||
const task = await taskRepository.createTask({
|
const task = await taskRepository.createTask({
|
||||||
id_project: parseInt(idProject),
|
id_project: parseInt(idProject),
|
||||||
|
slug: taskSlug,
|
||||||
title: payload.title,
|
title: payload.title,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
deadline_at: deadlineAt,
|
deadline_at: deadlineAt,
|
||||||
@@ -46,7 +50,7 @@ class TaskService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await notificationService.createNotificationForProjectInterns(
|
await notificationService.createNotificationForProjectInterns(
|
||||||
idProject,
|
project.id,
|
||||||
"Tugas Baru Ditambahkan",
|
"Tugas Baru Ditambahkan",
|
||||||
`Mentor telah membuat tugas baru: '${task.title}' di proyek '${project.project_name}'.`
|
`Mentor telah membuat tugas baru: '${task.title}' di proyek '${project.project_name}'.`
|
||||||
).catch(err => console.error("Failed to send task creation notifications:", err));
|
).catch(err => console.error("Failed to send task creation notifications:", err));
|
||||||
@@ -63,19 +67,19 @@ class TaskService {
|
|||||||
return error(new BadRequestError('Project ID must be a valid number'));
|
return error(new BadRequestError('Project ID must be a valid number'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await taskRepository.findProjectById(idProject);
|
const project = await taskRepository.findProjectByIdOrSlug(idProject);
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return error(new NotFoundError('Project not found'));
|
return error(new NotFoundError('Project not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessError = await this.validateProjectAccess(idProject, actor);
|
const accessError = await this.validateProjectAccess(project.id, actor);
|
||||||
|
|
||||||
if (accessError) {
|
if (accessError) {
|
||||||
return error(accessError);
|
return error(accessError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks = await taskRepository.findTasksByProject(idProject);
|
const tasks = await taskRepository.findTasksByProject(project.id);
|
||||||
const totalMembers = project.members?.length || 0;
|
const totalMembers = project.members?.length || 0;
|
||||||
|
|
||||||
const mappedTasks = tasks.map((task) => {
|
const mappedTasks = tasks.map((task) => {
|
||||||
@@ -104,13 +108,13 @@ class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTaskById(idTask, actor = {}) {
|
async getTaskById(idTask, actor = {}, projectIdentifier = null) {
|
||||||
try {
|
try {
|
||||||
if (!this.isPositiveInteger(idTask)) {
|
if (!idTask) {
|
||||||
return error(new BadRequestError('Task ID must be a valid number'));
|
return error(new BadRequestError('Task ID or slug is required'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const task = await taskRepository.findTaskById(idTask);
|
const task = await this.resolveTask(idTask, projectIdentifier);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return error(new NotFoundError('Task not found'));
|
return error(new NotFoundError('Task not found'));
|
||||||
@@ -136,7 +140,7 @@ class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTask(idTask, payload, actor = {}) {
|
async updateTask(idTask, payload, actor = {}, projectIdentifier = null) {
|
||||||
try {
|
try {
|
||||||
if (!this.isPositiveInteger(idTask)) {
|
if (!this.isPositiveInteger(idTask)) {
|
||||||
return error(new BadRequestError('Task ID must be a valid number'));
|
return error(new BadRequestError('Task ID must be a valid number'));
|
||||||
@@ -150,7 +154,7 @@ class TaskService {
|
|||||||
return error(new BadRequestError('At least one field is required to update task'));
|
return error(new BadRequestError('At least one field is required to update task'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingTask = await taskRepository.findTaskById(idTask);
|
const existingTask = await this.resolveTask(idTask, projectIdentifier);
|
||||||
|
|
||||||
if (!existingTask) {
|
if (!existingTask) {
|
||||||
return error(new NotFoundError('Task not found'));
|
return error(new NotFoundError('Task not found'));
|
||||||
@@ -162,7 +166,7 @@ class TaskService {
|
|||||||
return error(accessError);
|
return error(accessError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const submissionCount = await taskRepository.countSubmissionsByTask(idTask);
|
const submissionCount = await taskRepository.countSubmissionsByTask(existingTask.id);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
payload.submission_type &&
|
payload.submission_type &&
|
||||||
@@ -176,7 +180,17 @@ class TaskService {
|
|||||||
|
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
|
|
||||||
if (payload.title !== undefined) updateData.title = payload.title;
|
if (payload.title !== undefined) {
|
||||||
|
updateData.title = payload.title;
|
||||||
|
|
||||||
|
if (payload.title !== existingTask.title) {
|
||||||
|
updateData.slug = await this.generateUniqueTaskSlug(
|
||||||
|
existingTask.id_project,
|
||||||
|
payload.title,
|
||||||
|
existingTask.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (payload.description !== undefined) updateData.description = payload.description;
|
if (payload.description !== undefined) updateData.description = payload.description;
|
||||||
if (payload.submission_type !== undefined) updateData.submission_type = payload.submission_type;
|
if (payload.submission_type !== undefined) updateData.submission_type = payload.submission_type;
|
||||||
|
|
||||||
@@ -188,7 +202,7 @@ class TaskService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedTask = await taskRepository.updateTask(idTask, updateData);
|
const updatedTask = await taskRepository.updateTask(existingTask.id, updateData);
|
||||||
|
|
||||||
return data(this.mapTask(updatedTask));
|
return data(this.mapTask(updatedTask));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -196,7 +210,7 @@ class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTask(idTask, actor = {}) {
|
async deleteTask(idTask, actor = {}, projectIdentifier = null) {
|
||||||
try {
|
try {
|
||||||
if (!this.isPositiveInteger(idTask)) {
|
if (!this.isPositiveInteger(idTask)) {
|
||||||
return error(new BadRequestError('Task ID must be a valid number'));
|
return error(new BadRequestError('Task ID must be a valid number'));
|
||||||
@@ -206,7 +220,7 @@ class TaskService {
|
|||||||
return error(new ForbiddenError('Access denied: only admin or mentor can delete task'));
|
return error(new ForbiddenError('Access denied: only admin or mentor can delete task'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const task = await taskRepository.findTaskById(idTask);
|
const task = await this.resolveTask(idTask, projectIdentifier);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return error(new NotFoundError('Task not found'));
|
return error(new NotFoundError('Task not found'));
|
||||||
@@ -218,13 +232,13 @@ class TaskService {
|
|||||||
return error(accessError);
|
return error(accessError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const submissionCount = await taskRepository.countSubmissionsByTask(idTask);
|
const submissionCount = await taskRepository.countSubmissionsByTask(task.id);
|
||||||
|
|
||||||
if (submissionCount > 0) {
|
if (submissionCount > 0) {
|
||||||
return error(new ConflictError('Task already has submissions and cannot be deleted'));
|
return error(new ConflictError('Task already has submissions and cannot be deleted'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await taskRepository.deleteTask(idTask);
|
await taskRepository.deleteTask(task.id);
|
||||||
|
|
||||||
return data(null);
|
return data(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -232,7 +246,7 @@ class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitTask(idTask, payload, file, actor = {}) {
|
async submitTask(idTask, payload, file, actor = {}, projectIdentifier = null) {
|
||||||
try {
|
try {
|
||||||
if (!this.isPositiveInteger(idTask)) {
|
if (!this.isPositiveInteger(idTask)) {
|
||||||
return error(new BadRequestError('Task ID must be a valid number'));
|
return error(new BadRequestError('Task ID must be a valid number'));
|
||||||
@@ -242,7 +256,7 @@ class TaskService {
|
|||||||
return error(new ForbiddenError('Access denied: only intern can submit task'));
|
return error(new ForbiddenError('Access denied: only intern can submit task'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const task = await taskRepository.findTaskById(idTask);
|
const task = await this.resolveTask(idTask, projectIdentifier);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return error(new NotFoundError('Task not found'));
|
return error(new NotFoundError('Task not found'));
|
||||||
@@ -255,7 +269,7 @@ class TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const submissionData = {
|
const submissionData = {
|
||||||
id_task: parseInt(idTask),
|
id_task: parseInt(task.id),
|
||||||
id_user: parseInt(actor.id),
|
id_user: parseInt(actor.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -285,7 +299,7 @@ class TaskService {
|
|||||||
submissionData.url_link = payload.url_link;
|
submissionData.url_link = payload.url_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSubmission = await taskRepository.findSubmissionByTaskAndUser(idTask, actor.id);
|
const existingSubmission = await taskRepository.findSubmissionByTaskAndUser(task.id, actor.id);
|
||||||
|
|
||||||
let submission;
|
let submission;
|
||||||
|
|
||||||
@@ -518,6 +532,7 @@ class TaskService {
|
|||||||
return {
|
return {
|
||||||
id: task.id,
|
id: task.id,
|
||||||
id_project: task.id_project,
|
id_project: task.id_project,
|
||||||
|
slug: task.slug,
|
||||||
title: task.title,
|
title: task.title,
|
||||||
description: task.description,
|
description: task.description,
|
||||||
deadline_at: task.deadline_at,
|
deadline_at: task.deadline_at,
|
||||||
@@ -546,6 +561,7 @@ class TaskService {
|
|||||||
...this.mapTask(task),
|
...this.mapTask(task),
|
||||||
project: {
|
project: {
|
||||||
id: task.project?.id,
|
id: task.project?.id,
|
||||||
|
slug: task.project?.slug,
|
||||||
project_name: task.project?.project_name,
|
project_name: task.project?.project_name,
|
||||||
project_icon: task.project?.project_icon,
|
project_icon: task.project?.project_icon,
|
||||||
},
|
},
|
||||||
@@ -586,6 +602,7 @@ class TaskService {
|
|||||||
...this.mapTask(task),
|
...this.mapTask(task),
|
||||||
project: {
|
project: {
|
||||||
id: task.project?.id,
|
id: task.project?.id,
|
||||||
|
slug: task.project?.slug,
|
||||||
project_name: task.project?.project_name,
|
project_name: task.project?.project_name,
|
||||||
project_icon: task.project?.project_icon,
|
project_icon: task.project?.project_icon,
|
||||||
},
|
},
|
||||||
@@ -625,6 +642,38 @@ class TaskService {
|
|||||||
const number = Number(value);
|
const number = Number(value);
|
||||||
return Number.isInteger(number) && number > 0;
|
return Number.isInteger(number) && number > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateUniqueTaskSlug(idProject, title, excludeTaskId = null) {
|
||||||
|
const baseSlug = slugify(title, 'task');
|
||||||
|
|
||||||
|
let slug = baseSlug;
|
||||||
|
let counter = 2;
|
||||||
|
|
||||||
|
while (await taskRepository.findTaskSlugExists(idProject, slug, excludeTaskId)) {
|
||||||
|
slug = `${baseSlug}-${counter}`;
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveTask(identifier, projectIdentifier = null) {
|
||||||
|
if (/^\d+$/.test(String(identifier))) {
|
||||||
|
return taskRepository.findTaskById(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectIdentifier) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await taskRepository.findProjectByIdOrSlug(projectIdentifier);
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskRepository.findTaskBySlug(project.id, identifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new TaskService();
|
module.exports = new TaskService();
|
||||||
|
|||||||
Reference in New Issue
Block a user