Project management related changes
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package com.kifi.api.controller;
|
||||
|
||||
import com.kifi.api.entity.project.Project;
|
||||
import com.kifi.api.entity.project.ProjectTask;
|
||||
import com.kifi.api.entity.project.ProjectTaskComment;
|
||||
import com.kifi.api.service.ProjectService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/projects")
|
||||
@RequiredArgsConstructor
|
||||
public class ProjectController {
|
||||
|
||||
private final ProjectService projectService;
|
||||
|
||||
@GetMapping
|
||||
public Flux<Project> getProjects(Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return projectService.getProjectsByUser(userId);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<Project> createProject(@RequestBody CreateProjectRequest request, Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return projectService.createProject(
|
||||
userId,
|
||||
request.getName(),
|
||||
request.getDescription(),
|
||||
request.getCustomerId(),
|
||||
request.getBudget()
|
||||
);
|
||||
}
|
||||
|
||||
@PutMapping("/{projectId}")
|
||||
public Mono<Project> updateProject(@PathVariable Long projectId, @RequestBody CreateProjectRequest request, Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return projectService.updateProject(
|
||||
projectId,
|
||||
userId,
|
||||
request.getName(),
|
||||
request.getDescription(),
|
||||
request.getCustomerId(),
|
||||
request.getBudget(),
|
||||
request.getStatus()
|
||||
);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{projectId}")
|
||||
public Mono<Void> deleteProject(@PathVariable Long projectId, Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return projectService.deleteProject(projectId, userId);
|
||||
}
|
||||
|
||||
@GetMapping("/{projectId}/tasks")
|
||||
public Flux<ProjectTask> getTasks(@PathVariable Long projectId) {
|
||||
return projectService.getTasksByProject(projectId);
|
||||
}
|
||||
|
||||
@PostMapping("/{projectId}/tasks")
|
||||
public Mono<ProjectTask> createTask(@PathVariable Long projectId, @RequestBody CreateTaskRequest request) {
|
||||
return projectService.createTask(
|
||||
projectId,
|
||||
request.getTitle(),
|
||||
request.getDescription(),
|
||||
request.getAssigneeName(),
|
||||
request.getAssigneeUserId(),
|
||||
request.getIsBillable(),
|
||||
request.getHourlyRate()
|
||||
);
|
||||
}
|
||||
|
||||
@PutMapping("/tasks/{taskId}")
|
||||
public Mono<ProjectTask> updateTask(@PathVariable Long taskId, @RequestBody CreateTaskRequest request) {
|
||||
return projectService.updateTask(
|
||||
taskId,
|
||||
request.getTitle(),
|
||||
request.getDescription(),
|
||||
request.getAssigneeName(),
|
||||
request.getAssigneeUserId(),
|
||||
request.getStatus(),
|
||||
request.getIsBillable(),
|
||||
request.getHourlyRate(),
|
||||
request.getHoursLogged()
|
||||
);
|
||||
}
|
||||
|
||||
@DeleteMapping("/tasks/{taskId}")
|
||||
public Mono<Void> deleteTask(@PathVariable Long taskId) {
|
||||
return projectService.deleteTask(taskId);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/tasks/{taskId}/comments")
|
||||
public Flux<ProjectTaskComment> getComments(@PathVariable Long taskId) {
|
||||
return projectService.getCommentsForTask(taskId);
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/comments")
|
||||
public Mono<ProjectTaskComment> addComment(
|
||||
@PathVariable Long taskId,
|
||||
@RequestBody CreateCommentRequest request,
|
||||
Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return projectService.addComment(taskId, userId, request.getContent(), request.getImagesJson());
|
||||
}
|
||||
|
||||
@Data
|
||||
static class CreateProjectRequest {
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
private Long customerId;
|
||||
private java.math.BigDecimal budget;
|
||||
private String status;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class CreateTaskRequest {
|
||||
private String title;
|
||||
private String description;
|
||||
private String assigneeName;
|
||||
private Long assigneeUserId;
|
||||
private String status;
|
||||
private Boolean isBillable;
|
||||
private java.math.BigDecimal hourlyRate;
|
||||
private java.math.BigDecimal hoursLogged;
|
||||
}
|
||||
@Data
|
||||
static class CreateCommentRequest {
|
||||
private String content;
|
||||
private String imagesJson;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.controller;
|
||||
|
||||
import com.kifi.api.entity.User;
|
||||
import com.kifi.api.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/users")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping("/search")
|
||||
public Mono<ResponseEntity<List<User>>> searchUsers(@RequestParam String q) {
|
||||
return userService.searchUsers(q)
|
||||
.collectList()
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kifi.api.entity.project;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Table("projects")
|
||||
public class Project {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
private String description;
|
||||
private Long customerId;
|
||||
private Long userId;
|
||||
private java.math.BigDecimal budget;
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kifi.api.entity.project;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Table("project_tasks")
|
||||
public class ProjectTask {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long projectId;
|
||||
private String title;
|
||||
private String description;
|
||||
private String assigneeName;
|
||||
private Long assigneeUserId;
|
||||
private String status;
|
||||
private Boolean isBillable;
|
||||
private java.math.BigDecimal hourlyRate;
|
||||
private java.math.BigDecimal hoursLogged;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.kifi.api.entity.project;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Table("project_task_comments")
|
||||
public class ProjectTaskComment {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long taskId;
|
||||
private Long userId;
|
||||
private String content;
|
||||
private String images;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository;
|
||||
|
||||
import com.kifi.api.entity.project.Project;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProjectRepository extends ReactiveCrudRepository<Project, Long> {
|
||||
Flux<Project> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository;
|
||||
|
||||
import com.kifi.api.entity.project.ProjectTaskComment;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProjectTaskCommentRepository extends ReactiveCrudRepository<ProjectTaskComment, Long> {
|
||||
Flux<ProjectTaskComment> findByTaskIdOrderByCreatedAtAsc(Long taskId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository;
|
||||
|
||||
import com.kifi.api.entity.project.ProjectTask;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProjectTaskRepository extends ReactiveCrudRepository<ProjectTask, Long> {
|
||||
Flux<ProjectTask> findByProjectId(Long projectId);
|
||||
}
|
||||
@@ -10,4 +10,5 @@ import reactor.core.publisher.Mono;
|
||||
@Repository
|
||||
public interface UserRepository extends R2dbcRepository<User, Long> {
|
||||
Mono<User> findByEmail(String email);
|
||||
Flux<User> findByEmailContainingIgnoreCase(String email);
|
||||
}
|
||||
|
||||
117
kifi-api/src/main/java/com/kifi/api/service/ProjectService.java
Normal file
117
kifi-api/src/main/java/com/kifi/api/service/ProjectService.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package com.kifi.api.service;
|
||||
|
||||
import com.kifi.api.entity.project.Project;
|
||||
import com.kifi.api.entity.project.ProjectTask;
|
||||
import com.kifi.api.entity.project.ProjectTaskComment;
|
||||
import com.kifi.api.repository.ProjectRepository;
|
||||
import com.kifi.api.repository.ProjectTaskRepository;
|
||||
import com.kifi.api.repository.ProjectTaskCommentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ProjectService {
|
||||
|
||||
private final ProjectRepository projectRepository;
|
||||
private final ProjectTaskRepository projectTaskRepository;
|
||||
private final ProjectTaskCommentRepository projectTaskCommentRepository;
|
||||
|
||||
public Flux<Project> getProjectsByUser(Long userId) {
|
||||
return projectRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
public Mono<Project> createProject(Long userId, String name, String description, Long customerId, java.math.BigDecimal budget) {
|
||||
Project p = new Project();
|
||||
p.setUserId(userId);
|
||||
p.setName(name);
|
||||
p.setDescription(description);
|
||||
p.setCustomerId(customerId);
|
||||
p.setBudget(budget != null ? budget : java.math.BigDecimal.ZERO);
|
||||
p.setStatus("ACTIVE");
|
||||
p.setCreatedAt(LocalDateTime.now());
|
||||
return projectRepository.save(p);
|
||||
}
|
||||
|
||||
public Mono<Project> updateProject(Long projectId, Long userId, String name, String description, Long customerId, java.math.BigDecimal budget, String status) {
|
||||
return projectRepository.findById(projectId)
|
||||
.filter(p -> p.getUserId().equals(userId))
|
||||
.flatMap(p -> {
|
||||
if (name != null) p.setName(name);
|
||||
if (description != null) p.setDescription(description);
|
||||
if (customerId != null) p.setCustomerId(customerId);
|
||||
if (budget != null) p.setBudget(budget);
|
||||
if (status != null) p.setStatus(status);
|
||||
return projectRepository.save(p);
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Void> deleteProject(Long projectId, Long userId) {
|
||||
return projectRepository.findById(projectId)
|
||||
.filter(p -> p.getUserId().equals(userId))
|
||||
.flatMap(p -> projectTaskRepository.findByProjectId(projectId)
|
||||
.flatMap(task -> projectTaskRepository.delete(task))
|
||||
.then(projectRepository.delete(p))
|
||||
);
|
||||
}
|
||||
|
||||
public Flux<ProjectTask> getTasksByProject(Long projectId) {
|
||||
return projectTaskRepository.findByProjectId(projectId);
|
||||
}
|
||||
|
||||
public Mono<ProjectTask> createTask(Long projectId, String title, String description, String assigneeName, Long assigneeUserId, Boolean isBillable, java.math.BigDecimal hourlyRate) {
|
||||
ProjectTask t = new ProjectTask();
|
||||
t.setProjectId(projectId);
|
||||
t.setTitle(title);
|
||||
t.setDescription(description);
|
||||
t.setAssigneeName(assigneeName);
|
||||
t.setAssigneeUserId(assigneeUserId);
|
||||
t.setStatus("TODO");
|
||||
t.setIsBillable(isBillable != null ? isBillable : false);
|
||||
t.setHourlyRate(hourlyRate != null ? hourlyRate : java.math.BigDecimal.ZERO);
|
||||
t.setHoursLogged(java.math.BigDecimal.ZERO);
|
||||
t.setCreatedAt(LocalDateTime.now());
|
||||
return projectTaskRepository.save(t);
|
||||
}
|
||||
|
||||
public Mono<ProjectTask> updateTask(Long taskId, String title, String description, String assigneeName, Long assigneeUserId, String status, Boolean isBillable, java.math.BigDecimal hourlyRate, java.math.BigDecimal hoursLogged) {
|
||||
return projectTaskRepository.findById(taskId)
|
||||
.flatMap(t -> {
|
||||
if (title != null) t.setTitle(title);
|
||||
if (description != null) t.setDescription(description);
|
||||
if (assigneeName != null) t.setAssigneeName(assigneeName);
|
||||
if (assigneeUserId != null) t.setAssigneeUserId(assigneeUserId);
|
||||
if (status != null) t.setStatus(status);
|
||||
if (isBillable != null) t.setIsBillable(isBillable);
|
||||
if (hourlyRate != null) t.setHourlyRate(hourlyRate);
|
||||
if (hoursLogged != null) t.setHoursLogged(hoursLogged);
|
||||
return projectTaskRepository.save(t);
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Void> deleteTask(Long taskId) {
|
||||
return projectTaskRepository.deleteById(taskId);
|
||||
}
|
||||
|
||||
public Flux<ProjectTaskComment> getCommentsForTask(Long taskId) {
|
||||
return projectTaskCommentRepository.findByTaskIdOrderByCreatedAtAsc(taskId);
|
||||
}
|
||||
|
||||
public Mono<ProjectTaskComment> addComment(Long taskId, Long userId, String content, String imagesJson) {
|
||||
ProjectTaskComment comment = new ProjectTaskComment();
|
||||
comment.setTaskId(taskId);
|
||||
comment.setUserId(userId);
|
||||
comment.setContent(content);
|
||||
if (imagesJson != null && !imagesJson.isEmpty()) {
|
||||
comment.setImages(imagesJson);
|
||||
}
|
||||
comment.setCreatedAt(LocalDateTime.now());
|
||||
return projectTaskCommentRepository.save(comment);
|
||||
}
|
||||
}
|
||||
25
kifi-api/src/main/java/com/kifi/api/service/UserService.java
Normal file
25
kifi-api/src/main/java/com/kifi/api/service/UserService.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.service;
|
||||
|
||||
import com.kifi.api.entity.User;
|
||||
import com.kifi.api.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public Flux<User> searchUsers(String query) {
|
||||
if (query == null || query.isBlank()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return userRepository.findByEmailContainingIgnoreCase(query)
|
||||
.map(user -> {
|
||||
// Ensure password is not returned in search results
|
||||
user.setPassword(null);
|
||||
return user;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
spring:
|
||||
application:
|
||||
name: kifi-api
|
||||
|
||||
codec:
|
||||
max-in-memory-size: 10MB
|
||||
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://103.125.129.116:5333/kifi-v2
|
||||
|
||||
@@ -364,3 +364,37 @@ CREATE TABLE IF NOT EXISTS invoice_payments (
|
||||
emi_installment_number INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
customer_id INTEGER REFERENCES customers(id),
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
budget DECIMAL(15, 2) DEFAULT 0.00,
|
||||
status VARCHAR(50) DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
assignee_name VARCHAR(255),
|
||||
assignee_user_id INTEGER REFERENCES users(id),
|
||||
status VARCHAR(50) DEFAULT 'TODO', -- TODO, IN_PROGRESS, REVIEW, DONE
|
||||
is_billable BOOLEAN DEFAULT false,
|
||||
hourly_rate DECIMAL(15, 2) DEFAULT 0.00,
|
||||
hours_logged DECIMAL(10, 2) DEFAULT 0.00,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_task_comments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
task_id INTEGER REFERENCES project_tasks(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
content TEXT NOT NULL,
|
||||
images TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user