Commit e3976b1e by Augusto

MVP

parent 1356dcd8
-- SQL Migration: Add active field to users table
-- This script adds the 'active' field to the users table and sets all existing users as active
-- Add the active column to the users table
ALTER TABLE users
ADD COLUMN active BOOLEAN NOT NULL DEFAULT TRUE;
-- Create index on the active field for better query performance
CREATE INDEX active_idx ON users(active);
-- Update all existing users to be active (this is already the default, but being explicit)
UPDATE users SET active = TRUE WHERE active IS NULL;
-- Optional: Verify the changes
SELECT
id,
firstName,
lastName,
email,
user_role,
active,
createdAt
FROM users
LIMIT 5;
-- SQL Migration: Create System User for Automatic Comments
-- This script creates a system user that will be used to author system-generated comments
-- that cannot be deleted.
-- Insert system user if it doesn't exist
INSERT IGNORE INTO users (
id,
firstName,
lastName,
email,
user_role,
password,
createdAt,
updatedAt
) VALUES (
'system_user_001', -- You can use any unique ID here
'System',
'User',
'system@unike.com',
'ADMIN',
'system_user_no_login', -- This user should never be able to login
NOW(),
NOW()
);
-- Optional: Verify the system user was created
SELECT
id,
firstName,
lastName,
email,
user_role,
createdAt
FROM users
WHERE email = 'system@unike.com';
ALTER TABLE `attachments` ADD `attachment_type` enum('GENERIC','PRODUCTION') DEFAULT 'GENERIC' NOT NULL;--> statement-breakpoint
ALTER TABLE `occurrences` ADD CONSTRAINT `occurrences_reference_unique` UNIQUE(`reference`);
\ No newline at end of file
ALTER TABLE `occurrences` MODIFY COLUMN `reference` varchar(500);
\ No newline at end of file
ALTER TABLE `occurrences` ADD `awardDate` timestamp;--> statement-breakpoint
ALTER TABLE `occurrences` ADD `oniReporter` varchar(255);
\ No newline at end of file
ALTER TABLE `comments` MODIFY COLUMN `status` enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED') NOT NULL DEFAULT 'OPEN';--> statement-breakpoint
ALTER TABLE `occurrence_logs` MODIFY COLUMN `status` enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED') NOT NULL DEFAULT 'OPEN';--> statement-breakpoint
ALTER TABLE `occurrences` MODIFY COLUMN `status` enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED') NOT NULL DEFAULT 'OPEN';--> statement-breakpoint
ALTER TABLE `users` ADD `active` boolean DEFAULT true NOT NULL;--> statement-breakpoint
CREATE INDEX `active_idx` ON `users` (`active`);
\ No newline at end of file
{
"version": "5",
"dialect": "mysql",
"id": "8c0bb762-d60f-4bd4-9391-5a99700e6919",
"prevId": "eb296227-1602-457d-81cd-b6aaa05bbb03",
"tables": {
"assistants": {
"name": "assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"assistant_email_idx": {
"name": "assistant_email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"assistants_id": {
"name": "assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"assistants_email_unique": {
"name": "assistants_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
},
"attachments": {
"name": "attachments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"filename": {
"name": "filename",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"originalName": {
"name": "originalName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mimeType": {
"name": "mimeType",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"size": {
"name": "size",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"attachment_type": {
"name": "attachment_type",
"type": "enum('GENERIC','PRODUCTION')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'GENERIC'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"attachment_occurrence_idx": {
"name": "attachment_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"attachments_id": {
"name": "attachments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"comments": {
"name": "comments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"isInternal": {
"name": "isInternal",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"authorId": {
"name": "authorId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"occurrence_idx": {
"name": "occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"author_idx": {
"name": "author_idx",
"columns": [
"authorId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"comments_id": {
"name": "comments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"conclusions": {
"name": "conclusions",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"fieldMaterial": {
"name": "fieldMaterial",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"materialUsed": {
"name": "materialUsed",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"conclusion_occurrence_idx": {
"name": "conclusion_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"conclusions_id": {
"name": "conclusions_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"conclusions_occurrenceId_unique": {
"name": "conclusions_occurrenceId_unique",
"columns": [
"occurrenceId"
]
}
},
"checkConstraint": {}
},
"occurrence_assistants": {
"name": "occurrence_assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assistantId": {
"name": "assistantId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assignedBy": {
"name": "assignedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"occurrence_assistant_occurrence_idx": {
"name": "occurrence_assistant_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"occurrence_assistant_assistant_idx": {
"name": "occurrence_assistant_assistant_idx",
"columns": [
"assistantId"
],
"isUnique": false
},
"occurrence_assistant_assigned_by_idx": {
"name": "occurrence_assistant_assigned_by_idx",
"columns": [
"assignedBy"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_assistants_id": {
"name": "occurrence_assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"unique_occurrence_assistant": {
"name": "unique_occurrence_assistant",
"columns": [
"occurrenceId",
"assistantId"
]
}
},
"checkConstraint": {}
},
"occurrence_logs": {
"name": "occurrence_logs",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"log_action": {
"name": "log_action",
"type": "enum('CREATED','UPDATED','ASSIGNED','DUAL_ASSIGNED','UNASSIGNED','STATUS_CHANGED','PRIORITY_CHANGED','COMMENT_ADDED','ATTACHMENT_ADDED','ATTACHMENT_REMOVED','CONCLUSION_ADDED','CONCLUSION_UPDATED','ASSISTANT_ASSIGNED','ASSISTANT_UNASSIGNED','DELETED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"oldValue": {
"name": "oldValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"newValue": {
"name": "newValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"performedBy": {
"name": "performedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"log_occurrence_idx": {
"name": "log_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"log_performed_by_idx": {
"name": "log_performed_by_idx",
"columns": [
"performedBy"
],
"isUnique": false
},
"log_action_idx": {
"name": "log_action_idx",
"columns": [
"log_action"
],
"isUnique": false
},
"log_created_at_idx": {
"name": "log_created_at_idx",
"columns": [
"createdAt"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_logs_id": {
"name": "occurrence_logs_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"occurrences": {
"name": "occurrences",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"priority": {
"name": "priority",
"type": "enum('LOW','MEDIUM','HIGH','URGENT')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'MEDIUM'"
},
"category": {
"name": "category",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"location": {
"name": "location",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"latitude": {
"name": "latitude",
"type": "decimal(10,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"longitude": {
"name": "longitude",
"type": "decimal(11,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reference": {
"name": "reference",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reporterId": {
"name": "reporterId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assigneeId": {
"name": "assigneeId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"managerId": {
"name": "managerId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
},
"closedAt": {
"name": "closedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"reporter_idx": {
"name": "reporter_idx",
"columns": [
"reporterId"
],
"isUnique": false
},
"assignee_idx": {
"name": "assignee_idx",
"columns": [
"assigneeId"
],
"isUnique": false
},
"manager_idx": {
"name": "manager_idx",
"columns": [
"managerId"
],
"isUnique": false
},
"status_idx": {
"name": "status_idx",
"columns": [
"status"
],
"isUnique": false
},
"location_idx": {
"name": "location_idx",
"columns": [
"latitude",
"longitude"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrences_id": {
"name": "occurrences_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"occurrences_reference_unique": {
"name": "occurrences_reference_unique",
"columns": [
"reference"
]
}
},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"firstName": {
"name": "firstName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_role": {
"name": "user_role",
"type": "enum('USER','MODERATOR','ADMIN')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"email_idx": {
"name": "email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}
\ No newline at end of file
{
"version": "5",
"dialect": "mysql",
"id": "004e1dd1-8279-4548-8e50-4ba553ad7243",
"prevId": "8c0bb762-d60f-4bd4-9391-5a99700e6919",
"tables": {
"assistants": {
"name": "assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"assistant_email_idx": {
"name": "assistant_email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"assistants_id": {
"name": "assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"assistants_email_unique": {
"name": "assistants_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
},
"attachments": {
"name": "attachments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"filename": {
"name": "filename",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"originalName": {
"name": "originalName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mimeType": {
"name": "mimeType",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"size": {
"name": "size",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"attachment_type": {
"name": "attachment_type",
"type": "enum('GENERIC','PRODUCTION')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'GENERIC'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"attachment_occurrence_idx": {
"name": "attachment_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"attachments_id": {
"name": "attachments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"comments": {
"name": "comments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"isInternal": {
"name": "isInternal",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"authorId": {
"name": "authorId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"occurrence_idx": {
"name": "occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"author_idx": {
"name": "author_idx",
"columns": [
"authorId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"comments_id": {
"name": "comments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"conclusions": {
"name": "conclusions",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"fieldMaterial": {
"name": "fieldMaterial",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"materialUsed": {
"name": "materialUsed",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"conclusion_occurrence_idx": {
"name": "conclusion_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"conclusions_id": {
"name": "conclusions_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"conclusions_occurrenceId_unique": {
"name": "conclusions_occurrenceId_unique",
"columns": [
"occurrenceId"
]
}
},
"checkConstraint": {}
},
"occurrence_assistants": {
"name": "occurrence_assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assistantId": {
"name": "assistantId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assignedBy": {
"name": "assignedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"occurrence_assistant_occurrence_idx": {
"name": "occurrence_assistant_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"occurrence_assistant_assistant_idx": {
"name": "occurrence_assistant_assistant_idx",
"columns": [
"assistantId"
],
"isUnique": false
},
"occurrence_assistant_assigned_by_idx": {
"name": "occurrence_assistant_assigned_by_idx",
"columns": [
"assignedBy"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_assistants_id": {
"name": "occurrence_assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"unique_occurrence_assistant": {
"name": "unique_occurrence_assistant",
"columns": [
"occurrenceId",
"assistantId"
]
}
},
"checkConstraint": {}
},
"occurrence_logs": {
"name": "occurrence_logs",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"log_action": {
"name": "log_action",
"type": "enum('CREATED','UPDATED','ASSIGNED','DUAL_ASSIGNED','UNASSIGNED','STATUS_CHANGED','PRIORITY_CHANGED','COMMENT_ADDED','ATTACHMENT_ADDED','ATTACHMENT_REMOVED','CONCLUSION_ADDED','CONCLUSION_UPDATED','ASSISTANT_ASSIGNED','ASSISTANT_UNASSIGNED','DELETED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"oldValue": {
"name": "oldValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"newValue": {
"name": "newValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"performedBy": {
"name": "performedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"log_occurrence_idx": {
"name": "log_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"log_performed_by_idx": {
"name": "log_performed_by_idx",
"columns": [
"performedBy"
],
"isUnique": false
},
"log_action_idx": {
"name": "log_action_idx",
"columns": [
"log_action"
],
"isUnique": false
},
"log_created_at_idx": {
"name": "log_created_at_idx",
"columns": [
"createdAt"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_logs_id": {
"name": "occurrence_logs_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"occurrences": {
"name": "occurrences",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"priority": {
"name": "priority",
"type": "enum('LOW','MEDIUM','HIGH','URGENT')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'MEDIUM'"
},
"category": {
"name": "category",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"location": {
"name": "location",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"latitude": {
"name": "latitude",
"type": "decimal(10,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"longitude": {
"name": "longitude",
"type": "decimal(11,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reference": {
"name": "reference",
"type": "varchar(500)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reporterId": {
"name": "reporterId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assigneeId": {
"name": "assigneeId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"managerId": {
"name": "managerId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
},
"closedAt": {
"name": "closedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"reporter_idx": {
"name": "reporter_idx",
"columns": [
"reporterId"
],
"isUnique": false
},
"assignee_idx": {
"name": "assignee_idx",
"columns": [
"assigneeId"
],
"isUnique": false
},
"manager_idx": {
"name": "manager_idx",
"columns": [
"managerId"
],
"isUnique": false
},
"status_idx": {
"name": "status_idx",
"columns": [
"status"
],
"isUnique": false
},
"location_idx": {
"name": "location_idx",
"columns": [
"latitude",
"longitude"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrences_id": {
"name": "occurrences_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"occurrences_reference_unique": {
"name": "occurrences_reference_unique",
"columns": [
"reference"
]
}
},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"firstName": {
"name": "firstName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_role": {
"name": "user_role",
"type": "enum('USER','MODERATOR','ADMIN')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"email_idx": {
"name": "email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}
\ No newline at end of file
{
"version": "5",
"dialect": "mysql",
"id": "8feacb9f-df08-494f-8ebd-0e2f92312bd3",
"prevId": "004e1dd1-8279-4548-8e50-4ba553ad7243",
"tables": {
"assistants": {
"name": "assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"assistant_email_idx": {
"name": "assistant_email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"assistants_id": {
"name": "assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"assistants_email_unique": {
"name": "assistants_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
},
"attachments": {
"name": "attachments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"filename": {
"name": "filename",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"originalName": {
"name": "originalName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mimeType": {
"name": "mimeType",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"size": {
"name": "size",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"attachment_type": {
"name": "attachment_type",
"type": "enum('GENERIC','PRODUCTION')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'GENERIC'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"attachment_occurrence_idx": {
"name": "attachment_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"attachments_id": {
"name": "attachments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"comments": {
"name": "comments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"isInternal": {
"name": "isInternal",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"authorId": {
"name": "authorId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"occurrence_idx": {
"name": "occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"author_idx": {
"name": "author_idx",
"columns": [
"authorId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"comments_id": {
"name": "comments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"conclusions": {
"name": "conclusions",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"fieldMaterial": {
"name": "fieldMaterial",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"materialUsed": {
"name": "materialUsed",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"conclusion_occurrence_idx": {
"name": "conclusion_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"conclusions_id": {
"name": "conclusions_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"conclusions_occurrenceId_unique": {
"name": "conclusions_occurrenceId_unique",
"columns": [
"occurrenceId"
]
}
},
"checkConstraint": {}
},
"occurrence_assistants": {
"name": "occurrence_assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assistantId": {
"name": "assistantId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assignedBy": {
"name": "assignedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"occurrence_assistant_occurrence_idx": {
"name": "occurrence_assistant_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"occurrence_assistant_assistant_idx": {
"name": "occurrence_assistant_assistant_idx",
"columns": [
"assistantId"
],
"isUnique": false
},
"occurrence_assistant_assigned_by_idx": {
"name": "occurrence_assistant_assigned_by_idx",
"columns": [
"assignedBy"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_assistants_id": {
"name": "occurrence_assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"unique_occurrence_assistant": {
"name": "unique_occurrence_assistant",
"columns": [
"occurrenceId",
"assistantId"
]
}
},
"checkConstraint": {}
},
"occurrence_logs": {
"name": "occurrence_logs",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"log_action": {
"name": "log_action",
"type": "enum('CREATED','UPDATED','ASSIGNED','DUAL_ASSIGNED','UNASSIGNED','STATUS_CHANGED','PRIORITY_CHANGED','COMMENT_ADDED','ATTACHMENT_ADDED','ATTACHMENT_REMOVED','CONCLUSION_ADDED','CONCLUSION_UPDATED','ASSISTANT_ASSIGNED','ASSISTANT_UNASSIGNED','DELETED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"oldValue": {
"name": "oldValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"newValue": {
"name": "newValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"performedBy": {
"name": "performedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"log_occurrence_idx": {
"name": "log_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"log_performed_by_idx": {
"name": "log_performed_by_idx",
"columns": [
"performedBy"
],
"isUnique": false
},
"log_action_idx": {
"name": "log_action_idx",
"columns": [
"log_action"
],
"isUnique": false
},
"log_created_at_idx": {
"name": "log_created_at_idx",
"columns": [
"createdAt"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_logs_id": {
"name": "occurrence_logs_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"occurrences": {
"name": "occurrences",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"priority": {
"name": "priority",
"type": "enum('LOW','MEDIUM','HIGH','URGENT')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'MEDIUM'"
},
"category": {
"name": "category",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"location": {
"name": "location",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"latitude": {
"name": "latitude",
"type": "decimal(10,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"longitude": {
"name": "longitude",
"type": "decimal(11,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reference": {
"name": "reference",
"type": "varchar(500)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"awardDate": {
"name": "awardDate",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"oniReporter": {
"name": "oniReporter",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reporterId": {
"name": "reporterId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assigneeId": {
"name": "assigneeId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"managerId": {
"name": "managerId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
},
"closedAt": {
"name": "closedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"reporter_idx": {
"name": "reporter_idx",
"columns": [
"reporterId"
],
"isUnique": false
},
"assignee_idx": {
"name": "assignee_idx",
"columns": [
"assigneeId"
],
"isUnique": false
},
"manager_idx": {
"name": "manager_idx",
"columns": [
"managerId"
],
"isUnique": false
},
"status_idx": {
"name": "status_idx",
"columns": [
"status"
],
"isUnique": false
},
"location_idx": {
"name": "location_idx",
"columns": [
"latitude",
"longitude"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrences_id": {
"name": "occurrences_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"occurrences_reference_unique": {
"name": "occurrences_reference_unique",
"columns": [
"reference"
]
}
},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"firstName": {
"name": "firstName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_role": {
"name": "user_role",
"type": "enum('USER','MODERATOR','ADMIN')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"email_idx": {
"name": "email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}
\ No newline at end of file
{
"version": "5",
"dialect": "mysql",
"id": "b1dbd2a6-a25e-45c2-a5d0-8a8746b4965d",
"prevId": "8feacb9f-df08-494f-8ebd-0e2f92312bd3",
"tables": {
"assistants": {
"name": "assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"assistant_email_idx": {
"name": "assistant_email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"assistants_id": {
"name": "assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"assistants_email_unique": {
"name": "assistants_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
},
"attachments": {
"name": "attachments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"filename": {
"name": "filename",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"originalName": {
"name": "originalName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mimeType": {
"name": "mimeType",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"size": {
"name": "size",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"attachment_type": {
"name": "attachment_type",
"type": "enum('GENERIC','PRODUCTION')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'GENERIC'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"attachment_occurrence_idx": {
"name": "attachment_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"attachments_id": {
"name": "attachments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"comments": {
"name": "comments",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"isInternal": {
"name": "isInternal",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"status": {
"name": "status",
"type": "enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"authorId": {
"name": "authorId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"occurrence_idx": {
"name": "occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"author_idx": {
"name": "author_idx",
"columns": [
"authorId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"comments_id": {
"name": "comments_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"conclusions": {
"name": "conclusions",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"fieldMaterial": {
"name": "fieldMaterial",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"materialUsed": {
"name": "materialUsed",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"conclusion_occurrence_idx": {
"name": "conclusion_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"conclusions_id": {
"name": "conclusions_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"conclusions_occurrenceId_unique": {
"name": "conclusions_occurrenceId_unique",
"columns": [
"occurrenceId"
]
}
},
"checkConstraint": {}
},
"occurrence_assistants": {
"name": "occurrence_assistants",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assistantId": {
"name": "assistantId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assignedBy": {
"name": "assignedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"occurrence_assistant_occurrence_idx": {
"name": "occurrence_assistant_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"occurrence_assistant_assistant_idx": {
"name": "occurrence_assistant_assistant_idx",
"columns": [
"assistantId"
],
"isUnique": false
},
"occurrence_assistant_assigned_by_idx": {
"name": "occurrence_assistant_assigned_by_idx",
"columns": [
"assignedBy"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_assistants_id": {
"name": "occurrence_assistants_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"unique_occurrence_assistant": {
"name": "unique_occurrence_assistant",
"columns": [
"occurrenceId",
"assistantId"
]
}
},
"checkConstraint": {}
},
"occurrence_logs": {
"name": "occurrence_logs",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"occurrenceId": {
"name": "occurrenceId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"log_action": {
"name": "log_action",
"type": "enum('CREATED','UPDATED','ASSIGNED','DUAL_ASSIGNED','UNASSIGNED','STATUS_CHANGED','PRIORITY_CHANGED','COMMENT_ADDED','ATTACHMENT_ADDED','ATTACHMENT_REMOVED','CONCLUSION_ADDED','CONCLUSION_UPDATED','ASSISTANT_ASSIGNED','ASSISTANT_UNASSIGNED','DELETED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"oldValue": {
"name": "oldValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"newValue": {
"name": "newValue",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"performedBy": {
"name": "performedBy",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"log_occurrence_idx": {
"name": "log_occurrence_idx",
"columns": [
"occurrenceId"
],
"isUnique": false
},
"log_performed_by_idx": {
"name": "log_performed_by_idx",
"columns": [
"performedBy"
],
"isUnique": false
},
"log_action_idx": {
"name": "log_action_idx",
"columns": [
"log_action"
],
"isUnique": false
},
"log_created_at_idx": {
"name": "log_created_at_idx",
"columns": [
"createdAt"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrence_logs_id": {
"name": "occurrence_logs_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"occurrences": {
"name": "occurrences",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "enum('OPEN','ASSIGNED','IN_PROGRESS','RESOLVED','PAUSED','CLOSED','PARCIAL_RESOLVED','CANCELLED')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'OPEN'"
},
"priority": {
"name": "priority",
"type": "enum('LOW','MEDIUM','HIGH','URGENT')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'MEDIUM'"
},
"category": {
"name": "category",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"location": {
"name": "location",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"latitude": {
"name": "latitude",
"type": "decimal(10,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"longitude": {
"name": "longitude",
"type": "decimal(11,8)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reference": {
"name": "reference",
"type": "varchar(500)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"awardDate": {
"name": "awardDate",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"oniReporter": {
"name": "oniReporter",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reporterId": {
"name": "reporterId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"assigneeId": {
"name": "assigneeId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"managerId": {
"name": "managerId",
"type": "varchar(25)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
},
"closedAt": {
"name": "closedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"reporter_idx": {
"name": "reporter_idx",
"columns": [
"reporterId"
],
"isUnique": false
},
"assignee_idx": {
"name": "assignee_idx",
"columns": [
"assigneeId"
],
"isUnique": false
},
"manager_idx": {
"name": "manager_idx",
"columns": [
"managerId"
],
"isUnique": false
},
"status_idx": {
"name": "status_idx",
"columns": [
"status"
],
"isUnique": false
},
"location_idx": {
"name": "location_idx",
"columns": [
"latitude",
"longitude"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"occurrences_id": {
"name": "occurrences_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"occurrences_reference_unique": {
"name": "occurrences_reference_unique",
"columns": [
"reference"
]
}
},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "varchar(25)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"firstName": {
"name": "firstName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_role": {
"name": "user_role",
"type": "enum('USER','MODERATOR','ADMIN')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"email_idx": {
"name": "email_idx",
"columns": [
"email"
],
"isUnique": false
},
"active_idx": {
"name": "active_idx",
"columns": [
"active"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}
\ No newline at end of file
......@@ -50,6 +50,34 @@
"when": 1757687180760,
"tag": "0006_past_supernaut",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1758636750679,
"tag": "0007_careful_felicia_hardy",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1758636891644,
"tag": "0008_flat_nighthawk",
"breakpoints": true
},
{
"idx": 9,
"version": "5",
"when": 1758637348278,
"tag": "0009_gigantic_mac_gargan",
"breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1758791693736,
"tag": "0010_strange_gateway",
"breakpoints": true
}
]
}
\ No newline at end of file
......@@ -21,6 +21,7 @@ export const userRoleEnum = mysqlEnum('user_role', [
]);
export const occurrenceStatusEnum = mysqlEnum('status', [
'OPEN',
'ASSIGNED',
'IN_PROGRESS',
'RESOLVED',
'PAUSED',
......@@ -51,6 +52,10 @@ export const logActionEnum = mysqlEnum('log_action', [
'ASSISTANT_UNASSIGNED',
'DELETED',
]);
export const attachmentTypeEnum = mysqlEnum('attachment_type', [
'GENERIC',
'PRODUCTION',
]);
// Users table
export const users = mysqlTable(
......@@ -62,11 +67,13 @@ export const users = mysqlTable(
email: varchar('email', { length: 255 }).notNull().unique(),
user_role: userRoleEnum.notNull().default('USER'),
password: varchar('password', { length: 255 }).notNull(),
active: boolean('active').notNull().default(true),
createdAt: timestamp('createdAt').notNull().defaultNow(),
updatedAt: timestamp('updatedAt').notNull().defaultNow().onUpdateNow(),
},
(table) => ({
emailIdx: index('email_idx').on(table.email),
activeIdx: index('active_idx').on(table.active),
}),
);
......@@ -83,7 +90,9 @@ export const occurrences = mysqlTable(
location: varchar('location', { length: 255 }),
latitude: decimal('latitude', { precision: 10, scale: 8 }),
longitude: decimal('longitude', { precision: 11, scale: 8 }),
reference: text('reference'),
reference: varchar('reference', { length: 500 }).unique(),
awardDate: timestamp('awardDate'),
oniReporter: varchar('oniReporter', { length: 255 }),
reporterId: varchar('reporterId', { length: 25 }).notNull(),
assigneeId: varchar('assigneeId', { length: 25 }),
managerId: varchar('managerId', { length: 25 }),
......@@ -130,6 +139,7 @@ export const attachments = mysqlTable(
size: int('size').notNull(),
path: varchar('path', { length: 500 }).notNull(),
occurrenceId: varchar('occurrenceId', { length: 25 }).notNull(),
attachmentType: attachmentTypeEnum.notNull().default('GENERIC'),
createdAt: timestamp('createdAt').notNull().defaultNow(),
},
(table) => ({
......@@ -165,7 +175,7 @@ export const occurrenceLogs = mysqlTable(
oldValue: text('oldValue'),
newValue: text('newValue'),
performedBy: varchar('performedBy', { length: 25 }).notNull(),
status: occurrenceStatusEnum,
status: occurrenceStatusEnum.notNull().default('OPEN'),
createdAt: timestamp('createdAt').notNull().defaultNow(),
},
(table) => ({
......@@ -329,8 +339,10 @@ export type NewOccurrenceAssistant = typeof occurrenceAssistants.$inferInsert;
export type UserRole = 'USER' | 'MODERATOR' | 'ADMIN';
export type OccurrenceStatus =
| 'OPEN'
| 'ASSIGNED'
| 'IN_PROGRESS'
| 'RESOLVED'
| 'PAUSED'
| 'CLOSED'
| 'PARCIAL_RESOLVED'
| 'CANCELLED';
......@@ -351,6 +363,7 @@ export type LogAction =
| 'ASSISTANT_ASSIGNED'
| 'ASSISTANT_UNASSIGNED'
| 'DELETED';
export type AttachmentType = 'GENERIC' | 'PRODUCTION';
// Enum constants for runtime usage
export const UserRoleEnum = {
......@@ -361,8 +374,10 @@ export const UserRoleEnum = {
export const OccurrenceStatusEnum = {
OPEN: 'OPEN' as const,
ASSIGNED: 'ASSIGNED' as const,
IN_PROGRESS: 'IN_PROGRESS' as const,
RESOLVED: 'RESOLVED' as const,
PAUSED: 'PAUSED' as const,
CLOSED: 'CLOSED' as const,
PARCIAL_RESOLVED: 'PARCIAL_RESOLVED' as const,
CANCELLED: 'CANCELLED' as const,
......@@ -392,3 +407,8 @@ export const LogActionEnum = {
ASSISTANT_UNASSIGNED: 'ASSISTANT_UNASSIGNED' as const,
DELETED: 'DELETED' as const,
} as const;
export const AttachmentTypeEnum = {
GENERIC: 'GENERIC' as const,
PRODUCTION: 'PRODUCTION' as const,
} as const;
......@@ -82,8 +82,13 @@ export class AttachmentController {
async uploadFile(
@UploadedFile() file: Express.Multer.File,
@Body() uploadAttachmentDto: UploadAttachmentDto,
@CurrentUser() user: UserResponseDto,
): Promise<AttachmentResponseDto> {
return this.attachmentService.uploadFile(file, uploadAttachmentDto);
return this.attachmentService.uploadFile(
file,
uploadAttachmentDto,
user.id,
);
}
@Post('upload-multiple')
......@@ -128,10 +133,12 @@ export class AttachmentController {
async uploadMultipleFiles(
@UploadedFiles() files: Express.Multer.File[],
@Body() uploadAttachmentDto: UploadAttachmentDto,
@CurrentUser() user: UserResponseDto,
): Promise<AttachmentResponseDto[]> {
return this.attachmentService.uploadMultipleFiles(
files,
uploadAttachmentDto,
user.id,
);
}
......@@ -142,6 +149,12 @@ export class AttachmentController {
description: 'Occurrence unique identifier',
example: 'clxyz123abc456def',
})
@ApiQuery({
name: 'type',
required: false,
enum: ['GENERIC', 'PRODUCTION'],
description: 'Filter by attachment type',
})
@ApiResponse({
status: 200,
description: 'List of attachments for the occurrence',
......@@ -157,8 +170,12 @@ export class AttachmentController {
})
async findByOccurrence(
@Param('occurrenceId') occurrenceId: string,
@Query('type') attachmentType?: 'GENERIC' | 'PRODUCTION',
): Promise<AttachmentResponseDto[]> {
return this.attachmentService.findByOccurrence(occurrenceId);
return this.attachmentService.findByOccurrence(
occurrenceId,
attachmentType,
);
}
@Get('stats')
......@@ -169,6 +186,12 @@ export class AttachmentController {
type: String,
description: 'Filter stats by occurrence ID',
})
@ApiQuery({
name: 'type',
required: false,
enum: ['GENERIC', 'PRODUCTION'],
description: 'Filter by attachment type',
})
@ApiResponse({
status: 200,
description: 'Attachment statistics',
......@@ -185,6 +208,13 @@ export class AttachmentController {
'image/png': 7,
},
},
byAttachmentType: {
type: 'object',
example: {
GENERIC: 30,
PRODUCTION: 12,
},
},
},
},
})
......@@ -192,12 +222,16 @@ export class AttachmentController {
status: 401,
description: 'Unauthorized',
})
async getStats(@Query('occurrenceId') occurrenceId?: string): Promise<{
async getStats(
@Query('occurrenceId') occurrenceId?: string,
@Query('type') attachmentType?: 'GENERIC' | 'PRODUCTION',
): Promise<{
totalFiles: number;
totalSize: number;
byMimeType: Record<string, number>;
byAttachmentType: Record<string, number>;
}> {
return this.attachmentService.getStats(occurrenceId);
return this.attachmentService.getStats(occurrenceId, attachmentType);
}
@Get(':id')
......
......@@ -3,10 +3,12 @@ import { MulterModule } from '@nestjs/platform-express';
import { AttachmentService } from './attachment.service';
import { AttachmentController } from './attachment.controller';
import { DrizzleService } from '../../common/drizzle.service';
import { CommentModule } from '../comment/comment.module';
import { memoryStorage } from 'multer';
@Module({
imports: [
CommentModule,
MulterModule.register({
storage: memoryStorage(), // Store files in memory for processing
limits: {
......
......@@ -5,8 +5,9 @@ import {
InternalServerErrorException,
} from '@nestjs/common';
import { DrizzleService } from '../../common/drizzle.service';
import { CommentService } from '../comment/comment.service';
import { attachments, occurrences } from '../../drizzle/schema';
import { eq, desc, count, sum } from 'drizzle-orm';
import { eq, desc, count, sum, and } from 'drizzle-orm';
import { AttachmentResponseDto } from './dto/attachment-response.dto';
import { UploadAttachmentDto } from './dto/upload-attachment.dto';
import * as fs from 'fs';
......@@ -45,7 +46,10 @@ export class AttachmentService {
'application/x-7z-compressed',
];
constructor(private readonly drizzle: DrizzleService) {
constructor(
private readonly drizzle: DrizzleService,
private readonly commentService: CommentService,
) {
this.ensureUploadDirectory();
}
......@@ -59,6 +63,7 @@ export class AttachmentService {
async uploadFile(
file: Express.Multer.File,
uploadAttachmentDto: UploadAttachmentDto,
performedBy?: string,
): Promise<AttachmentResponseDto> {
// Validate file
this.validateFile(file);
......@@ -97,8 +102,28 @@ export class AttachmentService {
size: file.size,
path: filePath,
occurrenceId: uploadAttachmentDto.occurrenceId,
attachmentType: uploadAttachmentDto.attachmentType || 'GENERIC',
});
// Create system comment for attachment addition
try {
const attachmentTypeText =
uploadAttachmentDto.attachmentType === 'PRODUCTION'
? 'Produção'
: 'Genérico';
await this.commentService.createSystemComment(
uploadAttachmentDto.occurrenceId,
`Anexo adicionado (${attachmentTypeText}): ${file.originalname}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for attachment:',
commentError,
);
}
// Get the created attachment
const [attachment] = await this.drizzle.db
.select()
......@@ -126,6 +151,7 @@ export class AttachmentService {
async uploadMultipleFiles(
files: Express.Multer.File[],
uploadAttachmentDto: UploadAttachmentDto,
performedBy?: string,
): Promise<AttachmentResponseDto[]> {
if (!files || files.length === 0) {
throw new BadRequestException('No files provided');
......@@ -173,6 +199,7 @@ export class AttachmentService {
size: file.size,
path: filePath,
occurrenceId: uploadAttachmentDto.occurrenceId,
attachmentType: uploadAttachmentDto.attachmentType || 'GENERIC',
});
// Get the created attachment
......@@ -185,6 +212,28 @@ export class AttachmentService {
uploadedAttachments.push(attachment);
}
// Create system comment for multiple attachments
try {
const attachmentTypeText =
uploadAttachmentDto.attachmentType === 'PRODUCTION'
? 'Produção'
: 'Genérico';
const fileNames = uploadedAttachments
.map((att) => att.originalName)
.join(', ');
await this.commentService.createSystemComment(
uploadAttachmentDto.occurrenceId,
`Anexos adicionados (${attachmentTypeText}): ${fileNames}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for multiple attachments:',
commentError,
);
}
return uploadedAttachments;
} catch (error) {
// Clean up all uploaded files if any operation fails
......@@ -206,6 +255,7 @@ export class AttachmentService {
async findByOccurrence(
occurrenceId: string,
attachmentType?: 'GENERIC' | 'PRODUCTION',
): Promise<AttachmentResponseDto[]> {
// Validate that occurrence exists
const [occurrence] = await this.drizzle.db
......@@ -218,10 +268,18 @@ export class AttachmentService {
throw new NotFoundException('Occurrence not found');
}
// Build where conditions
const whereConditions: any[] = [eq(attachments.occurrenceId, occurrenceId)];
// Add type filter if provided
if (attachmentType) {
whereConditions.push(eq(attachments.attachmentType, attachmentType));
}
return this.drizzle.db
.select()
.from(attachments)
.where(eq(attachments.occurrenceId, occurrenceId))
.where(and(...whereConditions))
.orderBy(desc(attachments.createdAt));
}
......@@ -337,19 +395,32 @@ export class AttachmentService {
.slice(0, 100); // Limit length
}
async getStats(occurrenceId?: string): Promise<{
async getStats(
occurrenceId?: string,
attachmentType?: 'GENERIC' | 'PRODUCTION',
): Promise<{
totalFiles: number;
totalSize: number;
byMimeType: Record<string, number>;
byAttachmentType: Record<string, number>;
}> {
const whereClause = occurrenceId
? eq(attachments.occurrenceId, occurrenceId)
: undefined;
// Build where conditions
const whereConditions: any[] = [];
if (occurrenceId) {
whereConditions.push(eq(attachments.occurrenceId, occurrenceId));
}
if (attachmentType) {
whereConditions.push(eq(attachments.attachmentType, attachmentType));
}
const whereClause =
whereConditions.length > 0 ? and(...whereConditions) : undefined;
const attachmentList = await this.drizzle.db
.select({
size: attachments.size,
mimeType: attachments.mimeType,
attachmentType: attachments.attachmentType,
})
.from(attachments)
.where(whereClause);
......@@ -365,10 +436,19 @@ export class AttachmentService {
{} as Record<string, number>,
);
const byAttachmentType = attachmentList.reduce(
(acc, att) => {
acc[att.attachmentType] = (acc[att.attachmentType] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
return {
totalFiles,
totalSize,
byMimeType,
byAttachmentType,
};
}
}
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsEnum, IsOptional } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { AttachmentType, AttachmentTypeEnum } from '../../../types';
export class UploadAttachmentDto {
@ApiProperty({
......@@ -9,4 +10,14 @@ export class UploadAttachmentDto {
@IsString()
@IsNotEmpty()
occurrenceId: string;
@ApiPropertyOptional({
description: 'Type of attachment (generic or production)',
enum: AttachmentTypeEnum,
example: AttachmentTypeEnum.GENERIC,
default: AttachmentTypeEnum.GENERIC,
})
@IsOptional()
@IsEnum(AttachmentTypeEnum)
attachmentType?: AttachmentType = AttachmentTypeEnum.GENERIC;
}
......@@ -80,6 +80,12 @@ export class CommentController {
type: Number,
description: 'Number of items per page (default: 20)',
})
@ApiQuery({
name: 'order',
required: false,
enum: ['asc', 'desc'],
description: 'Sort order by creation date (default: asc)',
})
@ApiResponse({
status: 200,
description: 'List of comments for the occurrence',
......@@ -98,12 +104,14 @@ export class CommentController {
@CurrentUser() user: UserResponseDto,
@Query('page') page?: number,
@Query('limit') limit?: number,
@Query('order') order?: 'asc' | 'desc',
): Promise<CommentResponseDto[]> {
return this.commentService.findByOccurrence(
occurrenceId,
user.user_role,
page ? Number(page) : undefined,
limit ? Number(limit) : undefined,
order || 'asc',
);
}
......@@ -307,6 +315,12 @@ export class CommentController {
type: Number,
description: 'Number of items per page (default: 20)',
})
@ApiQuery({
name: 'order',
required: false,
enum: ['asc', 'desc'],
description: 'Sort order by creation date (default: asc)',
})
@ApiResponse({
status: 200,
description: 'List of internal comments for the occurrence',
......@@ -328,6 +342,7 @@ export class CommentController {
@Param('occurrenceId') occurrenceId: string,
@Query('page') page?: number,
@Query('limit') limit?: number,
@Query('order') order?: 'asc' | 'desc',
): Promise<CommentResponseDto[]> {
// Since this endpoint is restricted to admins/moderators, we pass ADMIN role
// which will show all comments including internal ones, then we'll filter
......@@ -336,6 +351,7 @@ export class CommentController {
UserRoleEnum.ADMIN,
page ? Number(page) : undefined,
limit ? Number(limit) : undefined,
order || 'asc',
);
// Filter to only internal comments
......
......@@ -10,7 +10,7 @@ import { UpdateCommentDto } from './dto/update-comment.dto';
import { CommentResponseDto } from './dto/comment-response.dto';
import { UserRole, UserRoleEnum } from '../../types';
import { comments, users, occurrences } from '../../drizzle/schema';
import { eq, and, desc, count } from 'drizzle-orm';
import { eq, and, desc, asc, count } from 'drizzle-orm';
@Injectable()
export class CommentService {
......@@ -93,6 +93,7 @@ export class CommentService {
userRole: UserRole,
page: number = 1,
limit: number = 20,
order: 'asc' | 'desc' = 'asc',
): Promise<CommentResponseDto[]> {
// Validate that occurrence exists
const [occurrence] = await this.drizzle.db
......@@ -138,7 +139,9 @@ export class CommentService {
.from(comments)
.leftJoin(users, eq(comments.authorId, users.id))
.where(and(...whereConditions))
.orderBy(comments.createdAt)
.orderBy(
order === 'desc' ? desc(comments.createdAt) : asc(comments.createdAt),
)
.limit(limit)
.offset(offset);
......@@ -254,6 +257,11 @@ export class CommentService {
throw new ForbiddenException('Access denied to internal comment');
}
// System comments cannot be deleted (identified by system user email)
if (comment.author.email === 'system@unike.com') {
throw new ForbiddenException('System comments cannot be deleted');
}
// Check if user can delete the comment
// Users can only delete their own comments, admins and moderators can delete any comment
if (
......@@ -297,4 +305,91 @@ export class CommentService {
return result.count;
}
async createSystemComment(
occurrenceId: string,
content: string,
isInternal: boolean = false,
performedBy?: string,
): Promise<CommentResponseDto> {
// Validate that occurrence exists
const [occurrence] = await this.drizzle.db
.select()
.from(occurrences)
.where(eq(occurrences.id, occurrenceId))
.limit(1);
if (!occurrence) {
throw new BadRequestException('Occurrence not found');
}
// Get or create system user
let systemUser = await this.getOrCreateSystemUser();
// If performedBy is provided, get user information and append to content
let finalContent = content;
if (performedBy) {
try {
const [user] = await this.drizzle.db
.select({
firstName: users.firstName,
lastName: users.lastName,
})
.from(users)
.where(eq(users.id, performedBy))
.limit(1);
if (user) {
finalContent = `${content} Por ${user.firstName} ${user.lastName}`;
}
} catch (error) {
console.error(
'Failed to get user information for system comment:',
error,
);
// Continue with original content if user lookup fails
}
}
// Create system comment
const commentId = this.drizzle.generateId();
await this.drizzle.db.insert(comments).values({
id: commentId,
content: finalContent,
isInternal: isInternal,
occurrenceId: occurrenceId,
authorId: systemUser.id,
});
return this.getCommentWithRelations(commentId);
}
private async getOrCreateSystemUser(): Promise<{
id: string;
email: string;
}> {
// Try to find existing system user
const [existingSystemUser] = await this.drizzle.db
.select()
.from(users)
.where(eq(users.email, 'system@unike.com'))
.limit(1);
if (existingSystemUser) {
return existingSystemUser;
}
// Create system user if it doesn't exist
const systemUserId = this.drizzle.generateId();
await this.drizzle.db.insert(users).values({
id: systemUserId,
firstName: 'System',
lastName: 'User',
email: 'system@unike.com',
user_role: 'ADMIN',
password: 'system_user_no_login', // This user should never be able to login
});
return { id: systemUserId, email: 'system@unike.com' };
}
}
......@@ -7,6 +7,7 @@ import {
MaxLength,
IsNumber,
IsDecimal,
IsDateString,
} from 'class-validator';
import {
OccurrenceStatus,
......@@ -70,7 +71,8 @@ export class CreateOccurrenceDto {
longitude?: number;
@ApiPropertyOptional({
description: 'Reference information for the occurrence location',
description:
'Reference information for the occurrence location (must be unique)',
example: 'Near the main entrance, next to the parking lot',
})
@IsOptional()
......@@ -78,6 +80,22 @@ export class CreateOccurrenceDto {
reference?: string;
@ApiPropertyOptional({
description: 'Award date for the occurrence',
example: '2024-01-15T10:30:00.000Z',
})
@IsOptional()
@IsDateString()
awardDate?: string;
@ApiPropertyOptional({
description: 'ONI reporter name',
example: 'John Smith',
})
@IsOptional()
@IsString()
oniReporter?: string;
@ApiPropertyOptional({
description: 'Occurrence priority level',
enum: PriorityEnum,
example: PriorityEnum.HIGH,
......
......@@ -23,7 +23,7 @@ export class LogResponseDto {
@ApiProperty({
description: 'Human-readable description of what happened',
example: 'Status changed from OPEN to IN_PROGRESS',
example: 'Status changed from OPEN to ASSIGNED',
})
description: string;
......@@ -35,7 +35,7 @@ export class LogResponseDto {
@ApiPropertyOptional({
description: 'New value after the change (if applicable)',
example: 'IN_PROGRESS',
example: 'ASSIGNED',
})
newValue?: string | null;
......@@ -47,7 +47,7 @@ export class LogResponseDto {
@ApiPropertyOptional({
description: 'Status of the occurrence at the time of this log entry',
example: 'IN_PROGRESS',
example: 'ASSIGNED',
})
status?: string | null;
......
......@@ -67,11 +67,23 @@ export class OccurrenceResponseDto {
longitude?: number | null;
@ApiPropertyOptional({
description: 'Reference information for the occurrence location',
description: 'Reference information for the occurrence location (unique)',
example: 'Near the main entrance, next to the parking lot',
})
reference?: string | null;
@ApiPropertyOptional({
description: 'Award date for the occurrence',
example: '2024-01-15T10:30:00.000Z',
})
awardDate?: Date | null;
@ApiPropertyOptional({
description: 'ONI reporter name',
example: 'John Smith',
})
oniReporter?: string | null;
@ApiProperty({
description: 'ID of the user who reported the occurrence',
example: 'clxyz123abc456def',
......
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsString, ArrayMinSize } from 'class-validator';
export class UnassignMultipleAssistantsDto {
@ApiProperty({
description: 'Array of assistant IDs to unassign from the occurrence',
example: ['clxyz123abc456def', 'clxyz789def012ghi', 'clxyz456ghi789jkl'],
type: [String],
})
@IsArray()
@ArrayMinSize(1, { message: 'At least one assistant ID must be provided' })
@IsString({ each: true })
assistantIds: string[];
}
......@@ -12,7 +12,7 @@ export class UpdateOccurrenceDto extends PartialType(CreateOccurrenceDto) {
@ApiPropertyOptional({
description: 'Occurrence status',
enum: OccurrenceStatusEnum,
example: OccurrenceStatusEnum.IN_PROGRESS,
example: OccurrenceStatusEnum.ASSIGNED,
})
@IsOptional()
@IsEnum(OccurrenceStatusEnum)
......
......@@ -28,7 +28,7 @@ export class OccurrenceLogService {
oldValue: oldValue || null,
newValue: newValue || null,
performedBy,
status: status || null,
status: status || 'OPEN',
});
}
......@@ -110,6 +110,7 @@ export class OccurrenceLogService {
performedBy,
oldStatus,
newStatus,
newStatus,
);
}
......
......@@ -29,6 +29,7 @@ import { AssignOccurrenceDto } from './dto/assign-occurrence.dto';
import { DualAssignOccurrenceDto } from './dto/dual-assign-occurrence.dto';
import { AssignAssistantDto } from './dto/assign-assistant.dto';
import { AssignMultipleAssistantsDto } from './dto/assign-multiple-assistants.dto';
import { UnassignMultipleAssistantsDto } from './dto/unassign-multiple-assistants.dto';
import { LogResponseDto } from './dto/log-response.dto';
import {
OccurrenceStatus,
......@@ -64,6 +65,10 @@ export class OccurrenceController {
status: 400,
description: 'Invalid input data or user not found',
})
@ApiResponse({
status: 409,
description: 'An occurrence with this reference already exists',
})
async create(
@Body() createOccurrenceDto: CreateOccurrenceDto,
): Promise<OccurrenceResponseDto> {
......@@ -216,6 +221,7 @@ export class OccurrenceController {
type: 'object',
properties: {
OPEN: { type: 'number', example: 25 },
ASSIGNED: { type: 'number', example: 15 },
IN_PROGRESS: { type: 'number', example: 30 },
RESOLVED: { type: 'number', example: 20 },
CLOSED: { type: 'number', example: 15 },
......@@ -364,11 +370,16 @@ export class OccurrenceController {
status: 400,
description: 'Invalid input data or user not found',
})
@ApiResponse({
status: 409,
description: 'An occurrence with this reference already exists',
})
async update(
@Param('id') id: string,
@Body() updateOccurrenceDto: UpdateOccurrenceDto,
@CurrentUser() user: UserResponseDto,
): Promise<OccurrenceResponseDto> {
return this.occurrenceService.update(id, updateOccurrenceDto);
return this.occurrenceService.update(id, updateOccurrenceDto, user.id);
}
@Patch(':id/assign')
......@@ -616,4 +627,42 @@ export class OccurrenceController {
async remove(@Param('id') id: string): Promise<void> {
return this.occurrenceService.remove(id);
}
@Patch(':id/unassign-multiple-assistants')
@ApiOperation({
summary: 'Unassign multiple assistants from occurrence',
description:
'Remove multiple assistants from the occurrence in a single operation. Creates only one system comment instead of multiple individual comments.',
})
@ApiParam({
name: 'id',
description: 'Occurrence unique identifier',
example: 'clxyz123abc456def',
})
@ApiBody({ type: UnassignMultipleAssistantsDto })
@ApiResponse({
status: 200,
description: 'Multiple assistants unassigned successfully',
type: OccurrenceResponseDto,
})
@ApiResponse({
status: 404,
description: 'Occurrence not found',
})
@ApiResponse({
status: 400,
description:
'No valid assistants found or none are assigned to this occurrence',
})
async unassignMultipleAssistants(
@Param('id') id: string,
@Body() unassignMultipleAssistantsDto: UnassignMultipleAssistantsDto,
@CurrentUser() user: UserResponseDto,
): Promise<OccurrenceResponseDto> {
return this.occurrenceService.unassignMultipleAssistants(
id,
unassignMultipleAssistantsDto.assistantIds,
user.id,
);
}
}
......@@ -3,8 +3,10 @@ import { OccurrenceService } from './occurrence.service';
import { OccurrenceController } from './occurrence.controller';
import { OccurrenceLogService } from './occurrence-log.service';
import { DrizzleService } from '../../common/drizzle.service';
import { CommentModule } from '../comment/comment.module';
@Module({
imports: [CommentModule],
controllers: [OccurrenceController],
providers: [OccurrenceService, OccurrenceLogService, DrizzleService],
exports: [OccurrenceService, OccurrenceLogService], // Export services for use in other modules
......
......@@ -2,6 +2,7 @@ import {
Injectable,
NotFoundException,
BadRequestException,
ConflictException,
} from '@nestjs/common';
import { DrizzleService } from '../../common/drizzle.service';
import { CreateOccurrenceDto } from './dto/create-occurrence.dto';
......@@ -10,6 +11,7 @@ import { OccurrenceResponseDto } from './dto/occurrence-response.dto';
import { AssignOccurrenceDto } from './dto/assign-occurrence.dto';
import { DualAssignOccurrenceDto } from './dto/dual-assign-occurrence.dto';
import { OccurrenceLogService } from './occurrence-log.service';
import { CommentService } from '../comment/comment.service';
import {
OccurrenceStatus,
Priority,
......@@ -25,13 +27,14 @@ import {
assistants,
occurrenceAssistants,
} from '../../drizzle/schema';
import { eq, and, or, like, desc, count, sql } from 'drizzle-orm';
import { eq, and, or, like, desc, count, sql, inArray } from 'drizzle-orm';
@Injectable()
export class OccurrenceService {
constructor(
private readonly drizzle: DrizzleService,
private readonly logService: OccurrenceLogService,
private readonly commentService: CommentService,
) {}
private async getOccurrenceWithRelations(
......@@ -50,6 +53,8 @@ export class OccurrenceService {
latitude: occurrences.latitude,
longitude: occurrences.longitude,
reference: occurrences.reference,
awardDate: occurrences.awardDate,
oniReporter: occurrences.oniReporter,
reporterId: occurrences.reporterId,
assigneeId: occurrences.assigneeId,
managerId: occurrences.managerId,
......@@ -183,19 +188,68 @@ export class OccurrenceService {
}
}
// Validate reference uniqueness if provided
if (createOccurrenceDto.reference) {
const [existingOccurrence] = await this.drizzle.db
.select()
.from(occurrences)
.where(eq(occurrences.reference, createOccurrenceDto.reference))
.limit(1);
if (existingOccurrence) {
throw new BadRequestException(
'An occurrence with this reference already exists',
);
}
}
// Create occurrence
const occurrenceId = this.drizzle.generateId();
try {
await this.drizzle.db.insert(occurrences).values({
id: occurrenceId,
...createOccurrenceDto,
});
} catch (error) {
// Handle database constraint violations
if (
error.code === 'ER_DUP_ENTRY' &&
error.message.includes('reference')
) {
throw new ConflictException(
'An occurrence with this reference already exists',
);
}
throw error;
}
// Log the creation
try {
await this.logService.logOccurrenceCreated(
occurrenceId,
createOccurrenceDto.reporterId,
createOccurrenceDto.title,
);
} catch (logError) {
// Log the error but don't fail the creation
console.error('Failed to log occurrence creation:', logError);
}
// Create system comment for occurrence creation
try {
const reporterName = `${reporter.firstName} ${reporter.lastName}`;
await this.commentService.createSystemComment(
occurrenceId,
`Ocorrência criada por ${reporterName}`,
true,
);
} catch (commentError) {
// Log the error but don't fail the creation
console.error(
'Failed to create system comment for occurrence creation:',
commentError,
);
}
// Get the full occurrence with relations
const occurrence = await this.getOccurrenceWithRelations(occurrenceId);
......@@ -251,6 +305,8 @@ export class OccurrenceService {
latitude: occurrences.latitude,
longitude: occurrences.longitude,
reference: occurrences.reference,
awardDate: occurrences.awardDate,
oniReporter: occurrences.oniReporter,
reporterId: occurrences.reporterId,
assigneeId: occurrences.assigneeId,
managerId: occurrences.managerId,
......@@ -286,9 +342,27 @@ export class OccurrenceService {
async update(
id: string,
updateOccurrenceDto: UpdateOccurrenceDto,
performedBy?: string,
): Promise<OccurrenceResponseDto> {
// Check if occurrence exists
await this.findOne(id);
// Get current occurrence to check for changes
const [currentOccurrence] = await this.drizzle.db
.select({
id: occurrences.id,
status: occurrences.status,
assigneeId: occurrences.assigneeId,
assignee: {
firstName: users.firstName,
lastName: users.lastName,
},
})
.from(occurrences)
.leftJoin(users, eq(occurrences.assigneeId, users.id))
.where(eq(occurrences.id, id))
.limit(1);
if (!currentOccurrence) {
throw new NotFoundException(`Occurrence with ID ${id} not found`);
}
// Validate assignee if provided
if (updateOccurrenceDto.assigneeId) {
......@@ -303,6 +377,26 @@ export class OccurrenceService {
}
}
// Validate reference uniqueness if provided
if (updateOccurrenceDto.reference) {
const [existingOccurrence] = await this.drizzle.db
.select()
.from(occurrences)
.where(
and(
eq(occurrences.reference, updateOccurrenceDto.reference),
sql`${occurrences.id} != ${id}`,
),
)
.limit(1);
if (existingOccurrence) {
throw new BadRequestException(
'An occurrence with this reference already exists',
);
}
}
// If status is being changed to CLOSED or RESOLVED, set closedAt
const updateData: any = { ...updateOccurrenceDto };
if (
......@@ -323,10 +417,80 @@ export class OccurrenceService {
updateData.closedAt = null;
}
try {
await this.drizzle.db
.update(occurrences)
.set(updateData)
.where(eq(occurrences.id, id));
} catch (error) {
// Handle database constraint violations
if (
error.code === 'ER_DUP_ENTRY' &&
error.message.includes('reference')
) {
throw new ConflictException(
'An occurrence with this reference already exists',
);
}
throw error;
}
// Create system comment for status change if status was updated
if (
updateOccurrenceDto.status &&
updateOccurrenceDto.status !== currentOccurrence.status
) {
try {
const statusMap = {
OPEN: 'Aberta',
ASSIGNED: 'Atribuída',
IN_PROGRESS: 'Em Progresso',
RESOLVED: 'Resolvida',
PAUSED: 'Pausada',
CLOSED: 'Fechada',
PARCIAL_RESOLVED: 'Parcialmente Resolvida',
CANCELLED: 'Cancelada',
};
const oldStatus =
statusMap[currentOccurrence.status] || currentOccurrence.status;
const newStatus =
statusMap[updateOccurrenceDto.status] || updateOccurrenceDto.status;
await this.commentService.createSystemComment(
id,
`Status alterado de "${oldStatus}" para "${newStatus}"`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for status change:',
commentError,
);
}
}
// Create system comments for assignee removal
if (
updateOccurrenceDto.assigneeId === null &&
currentOccurrence.assigneeId &&
currentOccurrence.assignee
) {
try {
await this.commentService.createSystemComment(
id,
`Desatribuído de ${currentOccurrence.assignee.firstName} ${currentOccurrence.assignee.lastName}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for assignee removal:',
commentError,
);
}
}
return this.getOccurrenceWithRelations(id);
}
......@@ -458,6 +622,8 @@ export class OccurrenceService {
latitude: occurrences.latitude,
longitude: occurrences.longitude,
reference: occurrences.reference,
awardDate: occurrences.awardDate,
oniReporter: occurrences.oniReporter,
reporterId: occurrences.reporterId,
assigneeId: occurrences.assigneeId,
managerId: occurrences.managerId,
......@@ -508,28 +674,35 @@ export class OccurrenceService {
.update(occurrences)
.set({
assigneeId: assignOccurrenceDto.assigneeId,
status: OccurrenceStatusEnum.IN_PROGRESS,
status: OccurrenceStatusEnum.ASSIGNED,
})
.where(eq(occurrences.id, occurrenceId));
// If there's an assignment note, create a comment
if (assignOccurrenceDto.assignmentNote) {
const commentId = this.drizzle.generateId();
await this.drizzle.db.insert(comments).values({
id: commentId,
content: `Assignment note: ${assignOccurrenceDto.assignmentNote}`,
isInternal: true,
occurrenceId: occurrenceId,
authorId: assignOccurrenceDto.assigneeId,
});
// Create system comment for assignment
try {
await this.commentService.createSystemComment(
occurrenceId,
`Atribuído a ${assignee.firstName} ${assignee.lastName}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for assignment:',
commentError,
);
}
// Log the assignment
try {
await this.logService.logAssignment(
occurrenceId,
performedBy,
`${assignee.firstName} ${assignee.lastName}`,
);
} catch (logError) {
console.error('Failed to log occurrence assignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......@@ -538,8 +711,25 @@ export class OccurrenceService {
occurrenceId: string,
performedBy: string,
): Promise<OccurrenceResponseDto> {
// Check if occurrence exists
await this.findOne(occurrenceId);
// Get current occurrence to get assignee info
const [currentOccurrence] = await this.drizzle.db
.select({
assigneeId: occurrences.assigneeId,
assignee: {
firstName: users.firstName,
lastName: users.lastName,
},
})
.from(occurrences)
.leftJoin(users, eq(occurrences.assigneeId, users.id))
.where(eq(occurrences.id, occurrenceId))
.limit(1);
if (!currentOccurrence) {
throw new NotFoundException(
`Occurrence with ID ${occurrenceId} not found`,
);
}
// Remove assignee
await this.drizzle.db
......@@ -550,8 +740,29 @@ export class OccurrenceService {
})
.where(eq(occurrences.id, occurrenceId));
// Create system comment for unassignment
try {
if (currentOccurrence.assigneeId && currentOccurrence.assignee) {
await this.commentService.createSystemComment(
occurrenceId,
`Desatribuído de ${currentOccurrence.assignee.firstName} ${currentOccurrence.assignee.lastName}`,
true,
performedBy,
);
}
} catch (commentError) {
console.error(
'Failed to create system comment for unassignment:',
commentError,
);
}
// Log the unassignment
try {
await this.logService.logUnassignment(occurrenceId, performedBy);
} catch (logError) {
console.error('Failed to log occurrence unassignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......@@ -612,8 +823,8 @@ export class OccurrenceService {
updateData.managerId = dualAssignOccurrenceDto.managerId;
}
// Set status to IN_PROGRESS if either assignee or manager is being assigned
updateData.status = OccurrenceStatusEnum.IN_PROGRESS;
// Set status to ASSIGNED if either assignee or manager is being assigned
updateData.status = OccurrenceStatusEnum.ASSIGNED;
// Update occurrence with provided fields
await this.drizzle.db
......@@ -621,24 +832,34 @@ export class OccurrenceService {
.set(updateData)
.where(eq(occurrences.id, occurrenceId));
// If there's an assignment note, create a comment
if (dualAssignOccurrenceDto.assignmentNote) {
const commentId = this.drizzle.generateId();
const authorId =
dualAssignOccurrenceDto.managerId ||
dualAssignOccurrenceDto.assigneeId ||
performedBy;
await this.drizzle.db.insert(comments).values({
id: commentId,
content: `Assignment note: ${dualAssignOccurrenceDto.assignmentNote}`,
isInternal: true,
occurrenceId: occurrenceId,
authorId: authorId,
});
// Create system comments for assignments
try {
if (dualAssignOccurrenceDto.assigneeId && assignee) {
await this.commentService.createSystemComment(
occurrenceId,
`Atribuído a ${assignee.firstName} ${assignee.lastName}`,
true,
performedBy,
);
}
if (dualAssignOccurrenceDto.managerId && manager) {
await this.commentService.createSystemComment(
occurrenceId,
`Gerrido por ${manager.firstName} ${manager.lastName}`,
true,
performedBy,
);
}
} catch (commentError) {
console.error(
'Failed to create system comments for assignments:',
commentError,
);
}
// Log the assignment based on what was updated
try {
if (assignee && manager) {
// Both assigned - dual assignment
await this.logService.logDualAssignment(
......@@ -662,6 +883,9 @@ export class OccurrenceService {
`Manager: ${manager.firstName} ${manager.lastName}`,
);
}
} catch (logError) {
console.error('Failed to log dual assignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......@@ -712,6 +936,21 @@ export class OccurrenceService {
assignedBy: performedBy,
});
// Create system comment for assistant assignment
try {
await this.commentService.createSystemComment(
occurrenceId,
`Auxiliar ${assistant.email} adicionado`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for assistant assignment:',
commentError,
);
}
// Get current occurrence status for logging
const [currentOccurrence] = await this.drizzle.db
.select({ status: occurrences.status })
......@@ -720,12 +959,16 @@ export class OccurrenceService {
.limit(1);
// Log the assistant assignment
try {
await this.logService.logAssistantAssigned(
occurrenceId,
performedBy,
assistant.email,
currentOccurrence?.status,
);
} catch (logError) {
console.error('Failed to log assistant assignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......@@ -773,6 +1016,23 @@ export class OccurrenceService {
),
);
// Create system comment for assistant removal
try {
if (assistant) {
await this.commentService.createSystemComment(
occurrenceId,
`Auxiliar ${assistant.email} removido`,
true,
performedBy,
);
}
} catch (commentError) {
console.error(
'Failed to create system comment for assistant removal:',
commentError,
);
}
// Get current occurrence status for logging
const [currentOccurrence] = await this.drizzle.db
.select({ status: occurrences.status })
......@@ -781,12 +1041,16 @@ export class OccurrenceService {
.limit(1);
// Log the assistant unassignment
try {
await this.logService.logAssistantUnassigned(
occurrenceId,
performedBy,
assistant?.email || 'Unknown Assistant',
currentOccurrence?.status,
);
} catch (logError) {
console.error('Failed to log assistant unassignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......@@ -857,6 +1121,22 @@ export class OccurrenceService {
await this.drizzle.db.insert(occurrenceAssistants).values(assignments);
// Create system comment for bulk assistant assignment
try {
const assistantEmails = foundAssistants.map((a) => a.email).join(', ');
await this.commentService.createSystemComment(
occurrenceId,
`Auxiliares adicionados: ${assistantEmails}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for bulk assistant assignment:',
commentError,
);
}
// Get current occurrence status for logging
const [currentOccurrence] = await this.drizzle.db
.select({ status: occurrences.status })
......@@ -865,6 +1145,7 @@ export class OccurrenceService {
.limit(1);
// Log the bulk assignment
try {
const assistantEmails = foundAssistants.map((a) => a.email).join(', ');
await this.logService.logAssistantAssigned(
occurrenceId,
......@@ -872,6 +1153,100 @@ export class OccurrenceService {
`Multiple Assistants: ${assistantEmails}`,
currentOccurrence?.status,
);
} catch (logError) {
console.error('Failed to log bulk assistant assignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
async unassignMultipleAssistants(
occurrenceId: string,
assistantIds: string[],
performedBy: string,
): Promise<OccurrenceResponseDto> {
// Check if occurrence exists
await this.findOne(occurrenceId);
// Get assistant info for logging and validation
const assistantRecords = await this.drizzle.db
.select()
.from(assistants)
.where(inArray(assistants.id, assistantIds));
if (assistantRecords.length === 0) {
throw new BadRequestException('No valid assistants found');
}
// Check which assistants are actually assigned to this occurrence
const assignedAssistants = await this.drizzle.db
.select({
assistantId: occurrenceAssistants.assistantId,
email: assistants.email,
})
.from(occurrenceAssistants)
.innerJoin(
assistants,
eq(occurrenceAssistants.assistantId, assistants.id),
)
.where(
and(
eq(occurrenceAssistants.occurrenceId, occurrenceId),
inArray(occurrenceAssistants.assistantId, assistantIds),
),
);
if (assignedAssistants.length === 0) {
throw new BadRequestException(
'None of the specified assistants are assigned to this occurrence',
);
}
// Remove all assignments
await this.drizzle.db
.delete(occurrenceAssistants)
.where(
and(
eq(occurrenceAssistants.occurrenceId, occurrenceId),
inArray(occurrenceAssistants.assistantId, assistantIds),
),
);
// Create single system comment for multiple assistant removal
try {
const assistantEmails = assignedAssistants.map((a) => a.email).join(', ');
await this.commentService.createSystemComment(
occurrenceId,
`Auxiliares removidos: ${assistantEmails}`,
true,
performedBy,
);
} catch (commentError) {
console.error(
'Failed to create system comment for multiple assistant removal:',
commentError,
);
}
// Get current occurrence status for logging
const [currentOccurrence] = await this.drizzle.db
.select({ status: occurrences.status })
.from(occurrences)
.where(eq(occurrences.id, occurrenceId))
.limit(1);
// Log the multiple assistant unassignment
try {
const assistantEmails = assignedAssistants.map((a) => a.email).join(', ');
await this.logService.logAssistantUnassigned(
occurrenceId,
performedBy,
`Multiple Assistants: ${assistantEmails}`,
currentOccurrence?.status,
);
} catch (logError) {
console.error('Failed to log bulk assistant unassignment:', logError);
}
return this.getOccurrenceWithRelations(occurrenceId);
}
......
......@@ -5,6 +5,8 @@ import {
IsNotEmpty,
IsString,
MinLength,
IsOptional,
IsBoolean,
} from 'class-validator';
import { UserRole, UserRoleEnum } from '../../../types';
......@@ -51,4 +53,13 @@ export class CreateUserDto {
})
@IsEnum(UserRoleEnum)
user_role?: UserRole = UserRoleEnum.USER;
@ApiProperty({
description: 'Whether the user is active or inactive',
example: true,
default: true,
})
@IsOptional()
@IsBoolean()
active?: boolean = true;
}
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty } from 'class-validator';
export class ToggleUserStatusDto {
@ApiProperty({
description: 'Whether the user should be active or inactive',
example: true,
})
@IsNotEmpty()
@IsBoolean()
active: boolean;
}
......@@ -34,6 +34,12 @@ export class UserResponseDto {
user_role: UserRole;
@ApiProperty({
description: 'Whether the user is active or inactive',
example: true,
})
active: boolean;
@ApiProperty({
description: 'User creation timestamp',
example: '2024-01-01T12:00:00.000Z',
})
......
......@@ -23,6 +23,7 @@ import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { AdminChangePasswordDto } from './dto/admin-change-password.dto';
import { ToggleUserStatusDto } from './dto/toggle-user-status.dto';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
@ApiTags('users')
......@@ -242,4 +243,32 @@ export class UserController {
async remove(@Param('id') id: string): Promise<void> {
return this.userService.remove(id);
}
@Patch(':id/toggle-status')
@ApiOperation({ summary: 'Toggle user active status' })
@ApiParam({
name: 'id',
description: 'User unique identifier',
example: 'clxyz123abc456def',
})
@ApiBody({ type: ToggleUserStatusDto })
@ApiResponse({
status: 200,
description: 'User status updated successfully',
type: UserResponseDto,
})
@ApiResponse({
status: 404,
description: 'User not found',
})
@ApiResponse({
status: 400,
description: 'Invalid input data',
})
async toggleUserStatus(
@Param('id') id: string,
@Body() toggleUserStatusDto: ToggleUserStatusDto,
): Promise<UserResponseDto> {
return this.userService.toggleUserStatus(id, toggleUserStatusDto);
}
}
......@@ -13,6 +13,7 @@ import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { AdminChangePasswordDto } from './dto/admin-change-password.dto';
import { ToggleUserStatusDto } from './dto/toggle-user-status.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
......@@ -43,6 +44,7 @@ export class UserService {
email: createUserDto.email,
user_role: createUserDto.user_role || 'USER',
password: hashedPassword,
active: createUserDto.active !== undefined ? createUserDto.active : true,
});
// Get the created user
......@@ -53,6 +55,7 @@ export class UserService {
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
......@@ -71,6 +74,7 @@ export class UserService {
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
......@@ -88,6 +92,7 @@ export class UserService {
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
......@@ -110,6 +115,7 @@ export class UserService {
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
......@@ -156,6 +162,7 @@ export class UserService {
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
......@@ -244,4 +251,44 @@ export class UserService {
return { message: 'Password changed successfully by admin' };
}
async toggleUserStatus(
userId: string,
toggleUserStatusDto: ToggleUserStatusDto,
): Promise<UserResponseDto> {
// Check if user exists
const [existingUser] = await this.drizzle.db
.select()
.from(users)
.where(eq(users.id, userId))
.limit(1);
if (!existingUser) {
throw new NotFoundException(`User with ID ${userId} not found`);
}
// Update user active status
await this.drizzle.db
.update(users)
.set({ active: toggleUserStatusDto.active })
.where(eq(users.id, userId));
// Get the updated user
const [user] = await this.drizzle.db
.select({
id: users.id,
firstName: users.firstName,
lastName: users.lastName,
email: users.email,
user_role: users.user_role,
active: users.active,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
.from(users)
.where(eq(users.id, userId))
.limit(1);
return user;
}
}
......@@ -2,6 +2,7 @@
export type UserRole = 'USER' | 'MODERATOR' | 'ADMIN';
export type OccurrenceStatus =
| 'OPEN'
| 'ASSIGNED'
| 'IN_PROGRESS'
| 'RESOLVED'
| 'PAUSED'
......@@ -25,6 +26,7 @@ export type LogAction =
| 'ASSISTANT_ASSIGNED'
| 'ASSISTANT_UNASSIGNED'
| 'DELETED';
export type AttachmentType = 'GENERIC' | 'PRODUCTION';
// Enum constants for runtime usage
export const UserRoleEnum = {
......@@ -35,6 +37,7 @@ export const UserRoleEnum = {
export const OccurrenceStatusEnum = {
OPEN: 'OPEN' as const,
ASSIGNED: 'ASSIGNED' as const,
IN_PROGRESS: 'IN_PROGRESS' as const,
RESOLVED: 'RESOLVED' as const,
PAUSED: 'PAUSED' as const,
......@@ -67,3 +70,8 @@ export const LogActionEnum = {
ASSISTANT_UNASSIGNED: 'ASSISTANT_UNASSIGNED' as const,
DELETED: 'DELETED' as const,
} as const;
export const AttachmentTypeEnum = {
GENERIC: 'GENERIC' as const,
PRODUCTION: 'PRODUCTION' as const,
} as const;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment