Add 'backend/' from commit '51bf07d5e16469036a8363e6b9d53616cbed643f'

git-subtree-dir: backend
git-subtree-mainline: 6dc2b48035ed90480e499efdb4513dff7b566616
git-subtree-split: 51bf07d5e1
This commit is contained in:
2026-03-18 19:23:20 -06:00
174 changed files with 19431 additions and 0 deletions

55
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,55 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea/*
!.idea/runConfigurations/
!.idea/.gitignore
!.idea/compiler.xml
!.idea/encodings.xml
!.idea/jarRepositories.xml
!.idea/misc.xml
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac ###
.DS_Store
### Project Specific ###
tmp/
uploads/
### Temp and backup files ###
*.backup
*.backup*
*.bak
*.tmp
*.py
temp_*.json
last_part.json
fix_*.py

13
backend/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
# Build
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn -q -DskipTests package
# Run
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

View File

@@ -0,0 +1,21 @@
services:
db:
image: mysql:8.0
container_name: petshop-db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: Petstoredb
MYSQL_USER: petshop
MYSQL_PASSWORD: petshop
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
timeout: 5s
retries: 30
volumes:
db_data:

View File

@@ -0,0 +1,23 @@
services:
db:
image: mysql:8.0
container_name: petshop-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: Petstoredb
MYSQL_USER: petshop
MYSQL_PASSWORD: petshop
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysql", "-uroot", "-proot", "-e", "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='Petstoredb' AND table_name='users';"]
interval: 10s
timeout: 5s
retries: 30
start_period: 40s
volumes:
db_data:

View File

@@ -0,0 +1,36 @@
services:
db:
image: mysql:8.0
container_name: petshop-db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: Petstoredb
MYSQL_USER: petshop
MYSQL_PASSWORD: petshop
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
timeout: 5s
retries: 30
api:
build: .
container_name: petshop-api
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: petshop
SPRING_DATASOURCE_PASSWORD: petshop
# Change this in real use (must be at least 32 characters)
JWT_SECRET: change_me_please_this_secret_key_is_long_enough_for_jwt_hmac_sha256
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
volumes:
db_data:

File diff suppressed because it is too large Load Diff

199
backend/pom.xml Normal file
View File

@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.3</version>
<relativePath/>
</parent>
<groupId>com.petshop</groupId>
<artifactId>backend</artifactId>
<version>1.0.0</version>
<name>PetShop Backend</name>
<description>Spring Boot backend for PetShop desktop application</description>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>25</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>require-java-25</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[25,)</version>
<message>JDK 25 or newer is required. Configure IntelliJ and Maven to use JDK 25 before running the backend.</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>dev-stack</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.petshop.backend.DevStackApplication</mainClass>
<classpathScope>runtime</classpathScope>
</configuration>
</execution>
<execution>
<id>reset-db</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.petshop.backend.ResetDatabaseApplication</mainClass>
<classpathScope>runtime</classpathScope>
</configuration>
</execution>
<execution>
<id>docker-up</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<arguments>
<argument>compose</argument>
<argument>-f</argument>
<argument>docker-compose.dev.yml</argument>
<argument>up</argument>
<argument>-d</argument>
<argument>--wait</argument>
<argument>db</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>docker-down</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<arguments>
<argument>compose</argument>
<argument>-f</argument>
<argument>docker-compose.dev.yml</argument>
<argument>down</argument>
<argument>-v</argument>
<argument>--remove-orphans</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

BIN
backend/postman/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,17 @@
package com.petshop.backend;
import com.petshop.backend.config.FlywayContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
@SpringBootApplication
@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
public class BackendApplication {
public static void main(String[] args) {
RuntimeClasspathValidator.validate();
new SpringApplicationBuilder(BackendApplication.class)
.initializers(new FlywayContextInitializer())
.run(args);
}
}

View File

@@ -0,0 +1,177 @@
package com.petshop.backend;
import com.petshop.backend.config.FlywayContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import java.net.BindException;
import java.time.Duration;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class DevStackApplication {
private static final Duration WATCH_INTERVAL = Duration.ofSeconds(2);
public static void main(String[] args) {
DockerComposeSupport docker = new DockerComposeSupport();
ConfigurableApplicationContext context = null;
CountDownLatch shutdownLatch = new CountDownLatch(1);
ScheduledExecutorService scheduler = null;
AtomicBoolean shuttingDown = new AtomicBoolean(false);
RuntimeException startupFailure = null;
try {
validateJavaVersion();
RuntimeClasspathValidator.validate();
docker.ensureDockerAvailable();
docker.startDatabase();
context = new SpringApplicationBuilder(BackendApplication.class)
.initializers(new FlywayContextInitializer())
.run(args);
context.addApplicationListener(event -> {
if (event instanceof ContextClosedEvent) {
shutdownLatch.countDown();
}
});
scheduler = startDatabaseWatch(docker, context, shuttingDown);
shutdownLatch.await();
} catch (RuntimeException ex) {
startupFailure = ex;
throw new IllegalStateException(describeStartupFailure(ex), ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Dev stack interrupted", ex);
} finally {
if (scheduler != null) {
scheduler.shutdownNow();
}
if (context != null && context.isActive()) {
context.close();
}
if (!shuttingDown.get()) {
try {
docker.stopDatabase();
} catch (RuntimeException stopFailure) {
if (startupFailure != null) {
System.err.println(stopFailure.getMessage());
} else {
throw stopFailure;
}
}
}
}
}
private static void validateJavaVersion() {
int feature = Runtime.version().feature();
if (feature < 25) {
throw new IllegalStateException("JDK 25 or newer is required. IntelliJ is currently using Java " + Runtime.version() + ".");
}
}
private static ScheduledExecutorService startDatabaseWatch(DockerComposeSupport docker, ConfigurableApplicationContext context, AtomicBoolean shuttingDown) {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleWithFixedDelay(() -> {
if (shuttingDown.get()) {
return;
}
if (!docker.isDatabaseRunning()) {
shuttingDown.set(true);
if (context.isActive()) {
context.close();
}
}
}, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS);
return scheduler;
}
private static String describeStartupFailure(RuntimeException ex) {
Throwable cause = deepestCause(ex);
String message = cause.getMessage();
String lowerMessage = "";
if (message != null) {
lowerMessage = message.toLowerCase(Locale.ROOT);
}
switch (classifyStartupFailure(cause, lowerMessage)) {
case SERVER_PORT_IN_USE:
return "Backend startup failed because port 8080 is already in use. Stop the other process or set SERVER_PORT to a different value.";
case DOCKER_NOT_RUNNING:
return "Backend startup failed because Docker is not available. Start Docker Desktop and try again.";
case DOCKER_NOT_INSTALLED:
return "Backend startup failed because Docker is not installed or not on PATH. Install Docker Desktop and reopen IntelliJ.";
case DATABASE_PORT_IN_USE:
return "Backend startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database and try again.";
case STALE_SCHEMA:
return "Backend startup failed because the database schema is stale or incomplete. Run Reset Database and then start Pet Shop Application again.";
case FLYWAY_MIGRATION_FAILED:
return "Backend startup failed during Flyway migration. Run Reset Database and check the database logs if the problem persists.";
case DATASOURCE_CONFIG_MISSING:
return "Backend startup failed because datasource configuration was not loaded. Reload the Maven project and rerun Pet Shop Application.";
case DATABASE_UNREACHABLE:
return "Backend startup failed because it could not connect to MySQL. Make sure Docker Desktop is running, the database container is healthy, and port 3306 is reachable.";
case UNKNOWN:
if (message == null || message.isBlank()) {
return "Backend startup failed: " + ex.getClass().getSimpleName();
}
return "Backend startup failed: " + message;
default:
throw new IllegalStateException("Unhandled startup failure classification");
}
}
private static StartupFailure classifyStartupFailure(Throwable cause, String lowerMessage) {
if (cause instanceof PortInUseException || cause instanceof BindException || lowerMessage.contains("port 8080") && lowerMessage.contains("already in use")) {
return StartupFailure.SERVER_PORT_IN_USE;
}
if (lowerMessage.contains("docker desktop") || lowerMessage.contains("docker daemon") || lowerMessage.contains("docker engine") || lowerMessage.contains("cannot connect to the docker daemon")) {
return StartupFailure.DOCKER_NOT_RUNNING;
}
if (lowerMessage.contains("docker executable") || lowerMessage.contains("no such file or directory") && lowerMessage.contains("docker")) {
return StartupFailure.DOCKER_NOT_INSTALLED;
}
if (lowerMessage.contains("port is already allocated") || lowerMessage.contains("address already in use") && lowerMessage.contains("3306")) {
return StartupFailure.DATABASE_PORT_IN_USE;
}
if (lowerMessage.contains("schema validation: missing table")) {
return StartupFailure.STALE_SCHEMA;
}
if (lowerMessage.contains("flyway") || lowerMessage.contains("migration")) {
return StartupFailure.FLYWAY_MIGRATION_FAILED;
}
if (lowerMessage.contains("datasource properties are required")) {
return StartupFailure.DATASOURCE_CONFIG_MISSING;
}
if (lowerMessage.contains("access denied for user") || lowerMessage.contains("communications link failure") || lowerMessage.contains("connection refused") || lowerMessage.contains("unable to determine dialect without jdbc metadata")) {
return StartupFailure.DATABASE_UNREACHABLE;
}
return StartupFailure.UNKNOWN;
}
private static Throwable deepestCause(Throwable throwable) {
Throwable current = throwable;
while (current.getCause() != null && current.getCause() != current) {
current = current.getCause();
}
return current;
}
private enum StartupFailure {
SERVER_PORT_IN_USE,
DOCKER_NOT_RUNNING,
DOCKER_NOT_INSTALLED,
DATABASE_PORT_IN_USE,
STALE_SCHEMA,
FLYWAY_MIGRATION_FAILED,
DATASOURCE_CONFIG_MISSING,
DATABASE_UNREACHABLE,
UNKNOWN
}
}

View File

@@ -0,0 +1,173 @@
package com.petshop.backend;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
final class DockerComposeSupport {
private final Path projectDir = Paths.get("").toAbsolutePath();
void ensureDockerAvailable() {
CommandResult versionResult = runCommand(List.of(resolveDockerExecutable(), "version"), false);
if (versionResult.exitCode != 0) {
throw new IllegalStateException(describeDockerFailure(versionResult.output));
}
}
void startDatabase() {
runCommand(composeCommand("up", "-d", "--wait", "db"));
}
void stopDatabase() {
runCommand(composeCommand("stop", "db"));
}
void resetDatabase() {
runCommand(composeCommand("down", "-v", "--remove-orphans"));
String volumeName = getDatabaseVolumeName();
if (volumeExists(volumeName)) {
runCommand(List.of(resolveDockerExecutable(), "volume", "rm", "-f", volumeName));
}
}
boolean isDatabaseRunning() {
CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false);
if (result.exitCode != 0) {
return false;
}
return result.output.lines()
.map(String::trim)
.anyMatch("db"::equals);
}
private boolean volumeExists(String volumeName) {
CommandResult result = runCommand(List.of(resolveDockerExecutable(), "volume", "ls", "--format", "{{.Name}}"), false);
if (result.exitCode != 0) {
return false;
}
return result.output.lines()
.map(String::trim)
.anyMatch(volumeName::equals);
}
private String getDatabaseVolumeName() {
return projectDir.getFileName().toString() + "_db_data";
}
private List<String> composeCommand(String... args) {
List<String> command = new ArrayList<>();
command.add(resolveDockerExecutable());
command.add("compose");
command.add("-f");
command.add("docker-compose.dev.yml");
for (String arg : args) {
command.add(arg);
}
return command;
}
private String resolveDockerExecutable() {
String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
if (os.contains("win")) {
Path dockerPath = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "bin", "docker.exe");
if (Files.isRegularFile(dockerPath)) {
return dockerPath.toString();
}
Path dockerPathAlt = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "docker.exe");
if (Files.isRegularFile(dockerPathAlt)) {
return dockerPathAlt.toString();
}
return "docker.exe";
}
return "docker";
}
private void runCommand(List<String> command) {
CommandResult result = runCommand(command, true);
if (result.exitCode != 0) {
throw new IllegalStateException(describeCommandFailure(command, result.output));
}
}
private CommandResult runCommand(List<String> command, boolean printOutput) {
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(projectDir.toFile());
builder.redirectErrorStream(true);
try {
Process process = builder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append(System.lineSeparator());
if (printOutput) {
System.out.println(line);
}
}
}
int exitCode = process.waitFor();
return new CommandResult(exitCode, output.toString().trim());
} catch (IOException ex) {
String executable = command.isEmpty() ? "docker" : command.getFirst();
throw new IllegalStateException("Unable to run " + executable + ". Install Docker Desktop and make sure it is available on PATH.", ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Docker command interrupted", ex);
}
}
private String describeDockerFailure(String output) {
String lowerOutput = "";
if (output != null) {
lowerOutput = output.toLowerCase(Locale.ROOT);
}
if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine") || lowerOutput.contains("cannot connect") || lowerOutput.contains("failed to connect") || lowerOutput.contains("docker_api") || lowerOutput.contains("docker api") || lowerOutput.contains("docker_engine") || lowerOutput.contains("pipe/docker_engine") || lowerOutput.contains("npipe")) {
return "Docker Desktop is not running. Start Docker Desktop and rerun the command.";
}
if (output == null || output.isBlank()) {
return "Docker is unavailable. Start Docker Desktop and rerun the command.";
}
return output;
}
private String describeCommandFailure(List<String> command, String output) {
String renderedCommand = String.join(" ", command);
String lowerOutput = "";
if (output != null) {
lowerOutput = output.toLowerCase(Locale.ROOT);
}
if (renderedCommand.contains(" up ") || renderedCommand.endsWith(" up -d --wait db")) {
if (lowerOutput.contains("port is already allocated") || lowerOutput.contains("address already in use")) {
return "Database startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database.";
}
if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine")) {
return "Database startup failed because Docker Desktop is not running.";
}
if (output == null || output.isBlank()) {
return "Database startup failed while bringing up the Docker MySQL container.";
}
return output;
}
if (renderedCommand.contains(" volume rm ")) {
if (output == null || output.isBlank()) {
return "Database reset failed while removing the Docker volume.";
}
return output;
}
if (output == null || output.isBlank()) {
return "Command failed: " + renderedCommand;
}
return output;
}
private record CommandResult(int exitCode, String output) {
}
}

View File

@@ -0,0 +1,10 @@
package com.petshop.backend;
public class ResetDatabaseApplication {
public static void main(String[] args) {
DockerComposeSupport docker = new DockerComposeSupport();
docker.ensureDockerAvailable();
docker.resetDatabase();
}
}

View File

@@ -0,0 +1,24 @@
package com.petshop.backend;
import java.net.URL;
final class RuntimeClasspathValidator {
private RuntimeClasspathValidator() {
}
static void validate() {
if (!resourceExists("application.yml")) {
throw new IllegalStateException("Backend resources are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration.");
}
if (!resourceExists("db/migration/V1__baseline_schema.sql")) {
throw new IllegalStateException("Flyway migration files are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration.");
}
}
private static boolean resourceExists(String path) {
ClassLoader classLoader = RuntimeClasspathValidator.class.getClassLoader();
URL resource = classLoader.getResource(path);
return resource != null;
}
}

View File

@@ -0,0 +1,167 @@
package com.petshop.backend.config;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.StoreAssignmentService;
import com.petshop.backend.service.UserBusinessLinkageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class DataInitializer implements CommandLineRunner {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserBusinessLinkageService userBusinessLinkageService;
private final StoreAssignmentService storeAssignmentService;
public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreAssignmentService storeAssignmentService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.userBusinessLinkageService = userBusinessLinkageService;
this.storeAssignmentService = storeAssignmentService;
}
@Override
public void run(String... args) {
System.out.println("==== DataInitializer: Starting user creation ====");
User admin = userRepository.findByUsername("admin").orElse(null);
if (admin == null) {
System.out.println("Creating admin user...");
admin = new User();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("admin123"));
admin.setEmail("admin@petshop.com");
admin.setFullName("Admin User");
admin.setPhone("000-000-1000");
admin.setRole(User.Role.ADMIN);
admin.setActive(true);
admin = userRepository.save(admin);
System.out.println("Admin user created successfully");
} else {
System.out.println("Admin user already exists");
// Normalize missing fields if needed
boolean updated = false;
if (admin.getFullName() == null || admin.getFullName().isEmpty()) {
admin.setFullName("Admin User");
updated = true;
}
if (admin.getEmail() == null || admin.getEmail().isEmpty()) {
admin.setEmail("admin@petshop.com");
updated = true;
}
if (admin.getPhone() == null || admin.getPhone().isEmpty()) {
admin.setPhone("000-000-1000");
updated = true;
}
if (admin.getActive() == null) {
admin.setActive(true);
updated = true;
}
if (admin.getRole() == null) {
admin.setRole(User.Role.ADMIN);
updated = true;
}
if (updated) {
admin = userRepository.save(admin);
System.out.println("Admin user normalized");
}
}
// Ensure linked employee
storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(admin), 1L);
User staff = userRepository.findByUsername("staff").orElse(null);
if (staff == null) {
System.out.println("Creating staff user...");
staff = new User();
staff.setUsername("staff");
staff.setPassword(passwordEncoder.encode("staff123"));
staff.setEmail("staff@petshop.com");
staff.setFullName("Staff User");
staff.setPhone("000-000-1001");
staff.setRole(User.Role.STAFF);
staff.setActive(true);
staff = userRepository.save(staff);
System.out.println("Staff user created successfully");
} else {
System.out.println("Staff user already exists");
// Normalize missing fields if needed
boolean updated = false;
if (staff.getFullName() == null || staff.getFullName().isEmpty()) {
staff.setFullName("Staff User");
updated = true;
}
if (staff.getEmail() == null || staff.getEmail().isEmpty()) {
staff.setEmail("staff@petshop.com");
updated = true;
}
if (staff.getPhone() == null || staff.getPhone().isEmpty()) {
staff.setPhone("000-000-1001");
updated = true;
}
if (staff.getActive() == null) {
staff.setActive(true);
updated = true;
}
if (staff.getRole() == null) {
staff.setRole(User.Role.STAFF);
updated = true;
}
if (updated) {
staff = userRepository.save(staff);
System.out.println("Staff user normalized");
}
}
// Ensure linked employee
storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(staff), 1L);
User customer = userRepository.findByUsername("customer").orElse(null);
if (customer == null) {
System.out.println("Creating customer user...");
customer = new User();
customer.setUsername("customer");
customer.setPassword(passwordEncoder.encode("customer123"));
customer.setEmail("customer@petshop.com");
customer.setFullName("Test Customer");
customer.setPhone("000-000-1002");
customer.setRole(User.Role.CUSTOMER);
customer.setActive(true);
customer = userRepository.save(customer);
System.out.println("Customer user created successfully");
} else {
System.out.println("Customer user already exists");
// Normalize missing fields if needed
boolean updated = false;
if (customer.getFullName() == null || customer.getFullName().isEmpty()) {
customer.setFullName("Test Customer");
updated = true;
}
if (customer.getEmail() == null || customer.getEmail().isEmpty()) {
customer.setEmail("customer@petshop.com");
updated = true;
}
if (customer.getPhone() == null || customer.getPhone().isEmpty()) {
customer.setPhone("000-000-1002");
updated = true;
}
if (customer.getActive() == null) {
customer.setActive(true);
updated = true;
}
if (customer.getRole() == null) {
customer.setRole(User.Role.CUSTOMER);
updated = true;
}
if (updated) {
customer = userRepository.save(customer);
System.out.println("Customer user normalized");
}
}
// Ensure linked customer
userBusinessLinkageService.ensureLinkedCustomer(customer);
System.out.println("==== DataInitializer: Completed ====");
}
}

View File

@@ -0,0 +1,40 @@
package com.petshop.backend.config;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationVersion;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Arrays;
public class FlywayContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String url = environment.getProperty("spring.datasource.url");
String username = environment.getProperty("spring.datasource.username");
String password = environment.getProperty("spring.datasource.password");
if (url == null || username == null || password == null) {
throw new IllegalStateException("Datasource properties are required before startup");
}
String[] locations = Arrays.stream(environment
.getProperty("spring.flyway.locations", "classpath:db/migration")
.split(","))
.map(String::trim)
.filter(location -> !location.isEmpty())
.toArray(String[]::new);
Flyway.configure()
.dataSource(url, username, password)
.locations(locations)
.baselineOnMigrate(environment.getProperty("spring.flyway.baseline-on-migrate", Boolean.class, false))
.baselineVersion(MigrationVersion.fromVersion(environment.getProperty("spring.flyway.baseline-version", "1")))
.load()
.migrate();
}
}

View File

@@ -0,0 +1,19 @@
package com.petshop.backend.config;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class TomcatPathToleranceConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setAllowBackslash(true);
connector.setEncodedReverseSolidusHandling("decode");
connector.setProperty("relaxedPathChars", "\\");
connector.setProperty("relaxedQueryChars", "\\");
});
}
}

View File

@@ -0,0 +1,91 @@
package com.petshop.backend.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TrailingSlashNormalizationFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String requestUri = request.getRequestURI();
if (requestUri == null || requestUri.isBlank()) {
return true;
}
return requestUri.equals(normalizePath(requestUri));
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String normalizedUri = normalizePath(request.getRequestURI());
String normalizedServletPath = normalizePath(request.getServletPath());
String normalizedPathInfo = normalizePath(request.getPathInfo());
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
@Override
public String getRequestURI() {
return normalizedUri;
}
@Override
public StringBuffer getRequestURL() {
String original = super.getRequestURL().toString();
int schemeSeparator = original.indexOf("://");
int pathStart = schemeSeparator >= 0 ? original.indexOf('/', schemeSeparator + 3) : original.indexOf('/');
if (pathStart < 0) {
return new StringBuffer(original);
}
String prefix = original.substring(0, pathStart);
return new StringBuffer(prefix + normalizedUri);
}
@Override
public String getServletPath() {
return normalizedServletPath;
}
@Override
public String getPathInfo() {
return normalizedPathInfo;
}
};
filterChain.doFilter(wrapper, response);
}
private String normalizePath(String value) {
if (value == null) {
return null;
}
String normalized = value.replace('\\', '/');
while (normalized.contains("//")) {
normalized = normalized.replace("//", "/");
}
if (shouldLowercase(normalized)) {
normalized = normalized.toLowerCase(java.util.Locale.ROOT);
}
int end = normalized.length();
while (end > 1 && normalized.charAt(end - 1) == '/') {
end--;
}
return normalized.substring(0, end);
}
private boolean shouldLowercase(String path) {
String lower = path.toLowerCase(java.util.Locale.ROOT);
return lower.startsWith("/api/")
|| lower.equals("/api")
|| lower.startsWith("/ws/")
|| lower.equals("/ws");
}
}

View File

@@ -0,0 +1,237 @@
package com.petshop.backend.config;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.AppPrincipal;
import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.service.ChatService;
import io.jsonwebtoken.JwtException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@Component
public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final ChatService chatService;
public WebSocketAuthChannelInterceptor(JwtUtil jwtUtil, UserRepository userRepository, ChatService chatService) {
this.jwtUtil = jwtUtil;
this.userRepository = userRepository;
this.chatService = chatService;
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getCommand();
if (command == null) {
return message;
}
if (StompCommand.CONNECT.equals(command)) {
String tokenHeader = firstHeader(accessor, "Authorization");
String token = extractToken(tokenHeader != null ? tokenHeader : firstHeader(accessor, "token"));
if (token == null || token.isBlank()) {
throw new IllegalArgumentException("Missing websocket token");
}
Long userId = extractUserId(token);
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
if (user == null) {
throw new IllegalArgumentException("User not found");
}
if (user.getActive() == null || !user.getActive()) {
throw new IllegalArgumentException("User account is inactive");
}
if (!jwtUtil.validateToken(token, user)) {
throw new IllegalArgumentException("Invalid websocket token");
}
AppPrincipal principal = new AppPrincipal(
user.getId(),
user.getUsername(),
user.getRole(),
user.getTokenVersion()
);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
principal,
null,
principal.getAuthorities()
);
accessor.setUser(authentication);
accessor.getSessionAttributes().put("user", authentication);
return message;
}
if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) {
return message;
}
User user = resolveUser(accessor.getUser(), accessor);
if (user == null) {
throw new IllegalArgumentException("Unauthenticated websocket session");
}
if (StompCommand.SUBSCRIBE.equals(command)) {
authorizeSubscription(accessor.getDestination(), user);
} else if (StompCommand.SEND.equals(command)) {
authorizeSend(accessor.getDestination(), user);
}
return message;
}
private User resolveUser(Principal principal, StompHeaderAccessor accessor) {
Principal currentPrincipal = principal;
if (currentPrincipal == null && accessor.getSessionAttributes() != null) {
Object sessionUser = accessor.getSessionAttributes().get("user");
if (sessionUser instanceof Principal storedPrincipal) {
accessor.setUser(storedPrincipal);
currentPrincipal = storedPrincipal;
}
}
if (currentPrincipal instanceof UsernamePasswordAuthenticationToken authenticationToken
&& authenticationToken.getPrincipal() instanceof AppPrincipal appPrincipal) {
return userRepository.findById(appPrincipal.getUserId())
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
if (currentPrincipal instanceof AppPrincipal appPrincipal) {
return userRepository.findById(appPrincipal.getUserId())
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
String tokenHeader = firstHeader(accessor, "Authorization");
String token = extractToken(tokenHeader != null ? tokenHeader : firstHeader(accessor, "token"));
if (token == null || token.isBlank()) {
return null;
}
Long userId = extractUserId(token);
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
if (user == null) {
throw new IllegalArgumentException("User not found");
}
if (user.getActive() == null || !user.getActive()) {
throw new IllegalArgumentException("User account is inactive");
}
if (!jwtUtil.validateToken(token, user)) {
throw new IllegalArgumentException("Invalid websocket token");
}
return user;
}
private void authorizeSubscription(String destination, User user) {
destination = normalizeDestination(destination);
if (destination == null || destination.startsWith("/user/queue/")) {
return;
}
if ("/topic/chat/conversations".equals(destination)) {
if (user.getRole() == User.Role.CUSTOMER) {
throw new IllegalArgumentException("Customers cannot subscribe to staff conversation feed");
}
return;
}
Long conversationId = extractConversationId(destination, "/topic/chat/conversations/");
if (conversationId != null && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
return;
}
throw new IllegalArgumentException("Not authorized to subscribe to destination");
}
private void authorizeSend(String destination, User user) {
destination = normalizeDestination(destination);
Long conversationId = extractConversationId(destination, "/app/chat/conversations/");
if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
return;
}
throw new IllegalArgumentException("Not authorized to send to destination");
}
private Long extractConversationId(String destination, String prefix) {
if (destination == null || !destination.startsWith(prefix)) {
return null;
}
String suffix = destination.substring(prefix.length());
String[] parts = suffix.split("/");
if (parts.length == 0 || parts[0].isBlank()) {
return null;
}
try {
return Long.parseLong(parts[0]);
} catch (NumberFormatException ex) {
return null;
}
}
private String firstHeader(StompHeaderAccessor accessor, String name) {
List<String> values = accessor.getNativeHeader(name);
if (values != null && !values.isEmpty()) {
return values.get(0);
}
for (String headerName : accessor.toNativeHeaderMap().keySet()) {
if (headerName.equalsIgnoreCase(name)) {
List<String> alternateValues = accessor.getNativeHeader(headerName);
return alternateValues == null || alternateValues.isEmpty() ? null : alternateValues.get(0);
}
}
return null;
}
private String extractToken(String rawValue) {
if (rawValue == null || rawValue.isBlank()) {
return null;
}
String normalized = rawValue.trim();
return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized;
}
private String normalizeDestination(String destination) {
if (destination == null || destination.isBlank()) {
return destination;
}
String normalized = destination.replace('\\', '/');
while (normalized.contains("//")) {
normalized = normalized.replace("//", "/");
}
return normalized.toLowerCase(Locale.ROOT);
}
private Long extractUserId(String token) {
try {
return jwtUtil.extractUserId(token);
} catch (JwtException | IllegalArgumentException ex) {
throw new IllegalArgumentException("Invalid websocket token: " + ex.getMessage(), ex);
}
}
public Map<String, Object> buildErrorPayload(Exception ex, String destination, Principal principal) {
Map<String, Object> response = new LinkedHashMap<>();
response.put("message", ex.getMessage() == null || ex.getMessage().isBlank() ? "WebSocket request failed" : ex.getMessage());
response.put("details", ex.getClass().getSimpleName());
response.put("destination", normalizeDestination(destination));
response.put("authenticated", principal != null);
return response;
}
}

View File

@@ -0,0 +1,45 @@
package com.petshop.backend.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor;
public WebSocketConfig(WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor) {
this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor;
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketAuthChannelInterceptor);
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/chat")
.setAllowedOriginPatterns("*");
registry.addEndpoint("/ws/chat/")
.setAllowedOriginPatterns("*");
registry.addEndpoint("/ws/chat-sockjs")
.setAllowedOriginPatterns("*")
.withSockJS();
registry.addEndpoint("/ws/chat-sockjs/")
.setAllowedOriginPatterns("*")
.withSockJS();
}
}

View File

@@ -0,0 +1,113 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.adoption.AdoptionRequest;
import com.petshop.backend.dto.adoption.AdoptionResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.AdoptionService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/adoptions")
public class AdoptionController {
private final AdoptionService adoptionService;
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public AdoptionController(AdoptionService adoptionService, UserRepository userRepository, CustomerRepository customerRepository) {
this.adoptionService = adoptionService;
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<Page<AdoptionResponse>> getAllAdoptions(
@RequestParam(required = false) String q,
Pageable pageable) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId));
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<AdoptionResponse> getAdoptionById(@PathVariable Long id) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
return ResponseEntity.ok(adoptionService.getAdoptionById(id, customerId));
}
@PostMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
if (!request.getCustomerId().equals(customer.getCustomerId())) {
throw new org.springframework.security.access.AccessDeniedException("You can only create adoptions for yourself");
}
}
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<AdoptionResponse> updateAdoption(
@PathVariable Long id,
@Valid @RequestBody AdoptionRequest request) {
return ResponseEntity.ok(adoptionService.updateAdoption(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> deleteAdoption(@PathVariable Long id) {
adoptionService.deleteAdoption(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
adoptionService.bulkDeleteAdoptions(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,26 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.analytics.DashboardResponse;
import com.petshop.backend.service.AnalyticsService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/analytics")
@PreAuthorize("hasRole('ADMIN')")
public class AnalyticsController {
private final AnalyticsService analyticsService;
public AnalyticsController(AnalyticsService analyticsService) {
this.analyticsService = analyticsService;
}
@GetMapping("/dashboard")
public ResponseEntity<DashboardResponse> getDashboard(
@RequestParam(defaultValue = "30") int days,
@RequestParam(defaultValue = "10") int top) {
return ResponseEntity.ok(analyticsService.getDashboardData(days, top));
}
}

View File

@@ -0,0 +1,125 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.appointment.AppointmentRequest;
import com.petshop.backend.dto.appointment.AppointmentResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.AppointmentService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/api/v1/appointments")
public class AppointmentController {
private final AppointmentService appointmentService;
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public AppointmentController(AppointmentService appointmentService, UserRepository userRepository, CustomerRepository customerRepository) {
this.appointmentService = appointmentService;
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<Page<AppointmentResponse>> getAllAppointments(
@RequestParam(required = false) String q,
Pageable pageable) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable, customerId));
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<AppointmentResponse> getAppointmentById(@PathVariable Long id) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
return ResponseEntity.ok(appointmentService.getAppointmentById(id, customerId));
}
@PostMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<AppointmentResponse> createAppointment(@Valid @RequestBody AppointmentRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
if (!request.getCustomerId().equals(customer.getCustomerId())) {
throw new org.springframework.security.access.AccessDeniedException("You can only create appointments for yourself");
}
}
return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<AppointmentResponse> updateAppointment(
@PathVariable Long id,
@Valid @RequestBody AppointmentRequest request) {
return ResponseEntity.ok(appointmentService.updateAppointment(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> deleteAppointment(@PathVariable Long id) {
appointmentService.deleteAppointment(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
appointmentService.bulkDeleteAppointments(request);
return ResponseEntity.noContent().build();
}
@GetMapping("/availability")
public ResponseEntity<List<String>> checkAvailability(
@RequestParam Long storeId,
@RequestParam Long serviceId,
@RequestParam String date) {
LocalDate appointmentDate = LocalDate.parse(date);
return ResponseEntity.ok(appointmentService.checkAvailability(storeId, serviceId, appointmentDate));
}
}

View File

@@ -0,0 +1,353 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.auth.AvatarUploadResponse;
import com.petshop.backend.dto.auth.LoginRequest;
import com.petshop.backend.dto.auth.LoginResponse;
import com.petshop.backend.dto.auth.ProfileUpdateRequest;
import com.petshop.backend.dto.auth.RegisterRequest;
import com.petshop.backend.dto.auth.RegisterResponse;
import com.petshop.backend.dto.auth.UserInfoResponse;
import com.petshop.backend.entity.EmployeeStore;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.EmployeeRepository;
import com.petshop.backend.repository.EmployeeStoreRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.service.UserBusinessLinkageService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
private final UserBusinessLinkageService userBusinessLinkageService;
private final EmployeeRepository employeeRepository;
private final EmployeeStoreRepository employeeStoreRepository;
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
this.passwordEncoder = passwordEncoder;
this.userBusinessLinkageService = userBusinessLinkageService;
this.employeeRepository = employeeRepository;
this.employeeStoreRepository = employeeStoreRepository;
}
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Username already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Email already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
String phone = trimToNull(request.getPhone());
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Phone already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEmail(request.getEmail());
user.setFullName(request.getFullName());
user.setPhone(phone);
user.setRole(User.Role.CUSTOMER);
user.setActive(true);
User savedUser = userRepository.save(user);
// Create or link customer record
userBusinessLinkageService.ensureLinkedCustomer(savedUser);
String token = jwtUtil.generateToken(savedUser);
return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse(
savedUser.getId(),
savedUser.getUsername(),
savedUser.getEmail(),
savedUser.getPhone(),
savedUser.getRole().name(),
token
));
}
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
String token = jwtUtil.generateToken(user);
return ResponseEntity.ok(new LoginResponse(
token,
user.getUsername(),
user.getRole().name()
));
} catch (BadCredentialsException e) {
Map<String, String> error = new HashMap<>();
error.put("message", "Invalid username or password");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
} catch (InternalAuthenticationServiceException e) {
if (e.getCause() instanceof DisabledException disabledException) {
Map<String, String> error = new HashMap<>();
error.put("message", disabledException.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
throw e;
} catch (DisabledException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
}
@GetMapping("/me")
public ResponseEntity<UserInfoResponse> getCurrentUser() {
User user = getAuthenticatedUser();
EmployeeStore employeeStore = resolveEmployeeStore(user);
return ResponseEntity.ok(new UserInfoResponse(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getFullName(),
user.getPhone(),
user.getAvatarUrl(),
user.getRole().name(),
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
employeeStore != null ? employeeStore.getStore().getStoreName() : null
));
}
@PutMapping("/me")
public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
User user = getAuthenticatedUser();
boolean invalidateToken = false;
if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) {
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Username already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
user.setUsername(request.getUsername());
invalidateToken = true;
}
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Email already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
user.setEmail(request.getEmail());
}
if (request.getFullName() != null) {
user.setFullName(request.getFullName());
}
if (request.getPhone() != null) {
String phone = trimToNull(request.getPhone());
if (!java.util.Objects.equals(phone, user.getPhone())) {
if (phone != null && userRepository.findByPhone(phone)
.filter(existing -> !existing.getId().equals(user.getId()))
.isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Phone already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
user.setPhone(phone);
}
}
if (request.getPassword() != null && !request.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true;
}
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
}
User updatedUser = userRepository.save(user);
userBusinessLinkageService.syncLinkedRecords(updatedUser);
EmployeeStore employeeStore = resolveEmployeeStore(updatedUser);
return ResponseEntity.ok(new UserInfoResponse(
updatedUser.getId(),
updatedUser.getUsername(),
updatedUser.getEmail(),
updatedUser.getFullName(),
updatedUser.getPhone(),
updatedUser.getAvatarUrl(),
updatedUser.getRole().name(),
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
employeeStore != null ? employeeStore.getStore().getStoreName() : null
));
}
private EmployeeStore resolveEmployeeStore(User user) {
if (user.getRole() == User.Role.CUSTOMER) {
return null;
}
return employeeRepository.findByUserId(user.getId())
.flatMap(employee -> employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()))
.orElse(null);
}
private String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
@PostMapping("/me/avatar")
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
User user = getAuthenticatedUser();
if (file.isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Please select a file to upload");
return ResponseEntity.badRequest().body(error);
}
if (file.getSize() > 5 * 1024 * 1024) {
Map<String, String> error = new HashMap<>();
error.put("message", "File size must not exceed 5MB");
return ResponseEntity.badRequest().body(error);
}
String contentType = file.getContentType();
if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) {
Map<String, String> error = new HashMap<>();
error.put("message", "Only JPG, PNG, and GIF images are allowed");
return ResponseEntity.badRequest().body(error);
}
try {
String uploadDir = "uploads/avatars";
File directory = new File(uploadDir);
if (!directory.exists()) {
directory.mkdirs();
}
String originalFilename = file.getOriginalFilename();
String extension = originalFilename != null && originalFilename.contains(".")
? originalFilename.substring(originalFilename.lastIndexOf("."))
: ".jpg";
String filename = UUID.randomUUID().toString() + extension;
Path filePath = Paths.get(uploadDir, filename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
String avatarUrl = "/uploads/avatars/" + filename;
user.setAvatarUrl(avatarUrl);
userRepository.save(user);
return ResponseEntity.ok(new AvatarUploadResponse(avatarUrl, "Avatar uploaded successfully"));
} catch (IOException e) {
Map<String, String> error = new HashMap<>();
error.put("message", "Failed to upload avatar: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
@GetMapping("/me/avatar")
public ResponseEntity<?> getAvatar() {
User user = getAuthenticatedUser();
if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("message", "No avatar uploaded");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
Map<String, String> response = new HashMap<>();
response.put("avatarUrl", user.getAvatarUrl());
return ResponseEntity.ok(response);
}
@DeleteMapping("/me/avatar")
public ResponseEntity<?> deleteAvatar() {
User user = getAuthenticatedUser();
if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) {
try {
Path filePath = Paths.get("." + user.getAvatarUrl());
Files.deleteIfExists(filePath);
} catch (IOException e) {
}
user.setAvatarUrl(null);
userRepository.save(user);
}
Map<String, String> response = new HashMap<>();
response.put("message", "Avatar deleted successfully");
return ResponseEntity.ok(response);
}
@PostMapping("/logout")
public ResponseEntity<?> logout() {
Map<String, String> response = new HashMap<>();
response.put("message", "Logged out successfully");
response.put("note", "Token remains valid until expiration. Clear token from client storage.");
return ResponseEntity.ok(response);
}
private User getAuthenticatedUser() {
try {
return AuthenticationHelper.getAuthenticatedUser(userRepository);
} catch (RuntimeException ex) {
throw new UsernameNotFoundException(ex.getMessage(), ex);
}
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.category.CategoryRequest;
import com.petshop.backend.dto.category.CategoryResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.service.CategoryService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/categories")
public class CategoryController {
private final CategoryService categoryService;
public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}
@GetMapping
public ResponseEntity<Page<CategoryResponse>> getAllCategories(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(categoryService.getAllCategories(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<CategoryResponse> getCategoryById(@PathVariable Long id) {
return ResponseEntity.ok(categoryService.getCategoryById(id));
}
@PostMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<CategoryResponse> createCategory(@Valid @RequestBody CategoryRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<CategoryResponse> updateCategory(
@PathVariable Long id,
@Valid @RequestBody CategoryRequest request) {
return ResponseEntity.ok(categoryService.updateCategory(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> deleteCategory(@PathVariable Long id) {
categoryService.deleteCategory(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
categoryService.bulkDeleteCategories(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,99 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.chat.ConversationRequest;
import com.petshop.backend.dto.chat.ConversationResponse;
import com.petshop.backend.dto.chat.MessageRequest;
import com.petshop.backend.dto.chat.MessageResponse;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.ChatRealtimeService;
import com.petshop.backend.service.ChatService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/chat")
public class ChatController {
private final ChatService chatService;
private final ChatRealtimeService chatRealtimeService;
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, CustomerRepository customerRepository) {
this.chatService = chatService;
this.chatRealtimeService = chatRealtimeService;
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
private User getCurrentUser() {
try {
return AuthenticationHelper.getAuthenticatedUser(userRepository);
} catch (RuntimeException ex) {
throw new UsernameNotFoundException(ex.getMessage(), ex);
}
}
@PostMapping("/conversations")
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<ConversationResponse> createConversation(@Valid @RequestBody ConversationRequest request) {
User user = getCurrentUser();
ConversationResponse response = chatService.createConversation(user.getId(), request);
chatRealtimeService.publishNewConversation(response);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@GetMapping("/conversations")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<List<ConversationResponse>> getConversations() {
User user = getCurrentUser();
List<ConversationResponse> conversations = chatService.getConversations(user.getId(), user.getRole());
return ResponseEntity.ok(conversations);
}
@GetMapping("/conversations/{id}")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<ConversationResponse> getConversation(@PathVariable Long id) {
User user = getCurrentUser();
ConversationResponse conversation = chatService.getConversation(id, user.getId(), user.getRole());
return ResponseEntity.ok(conversation);
}
@PostMapping("/conversations/{id}/messages")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<MessageResponse> sendMessage(
@PathVariable Long id,
@Valid @RequestBody MessageRequest request) {
User user = getCurrentUser();
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
chatRealtimeService.publishMessage(id, message);
chatRealtimeService.publishConversationUpdate(id);
return ResponseEntity.status(HttpStatus.CREATED).body(message);
}
@GetMapping("/conversations/{id}/messages")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<List<MessageResponse>> getMessages(@PathVariable Long id) {
User user = getCurrentUser();
List<MessageResponse> messages = chatService.getMessages(id, user.getId(), user.getRole());
return ResponseEntity.ok(messages);
}
@PostMapping("/conversations/{id}/request-human")
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<ConversationResponse> requestHumanTakeover(@PathVariable Long id) {
User user = getCurrentUser();
ConversationResponse conversation = chatService.requestHumanTakeover(id, user.getId(), user.getRole());
chatRealtimeService.publishConversationUpdate(id);
return ResponseEntity.ok(conversation);
}
}

View File

@@ -0,0 +1,123 @@
package com.petshop.backend.controller;
import com.petshop.backend.config.WebSocketAuthChannelInterceptor;
import com.petshop.backend.dto.chat.MessageRequest;
import com.petshop.backend.dto.chat.MessageResponse;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.AppPrincipal;
import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.service.ChatRealtimeService;
import com.petshop.backend.service.ChatService;
import jakarta.validation.Valid;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Controller
public class ChatWebSocketController {
private final ChatService chatService;
private final ChatRealtimeService chatRealtimeService;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor;
public ChatWebSocketController(
ChatService chatService,
ChatRealtimeService chatRealtimeService,
UserRepository userRepository,
JwtUtil jwtUtil,
WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor
) {
this.chatService = chatService;
this.chatRealtimeService = chatRealtimeService;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor;
}
@MessageMapping("/chat/conversations/{id}/messages")
@SendToUser("/queue/chat/errors")
public void sendMessage(@DestinationVariable Long id, @Valid @Payload MessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
User user = resolveUser(headerAccessor);
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
chatRealtimeService.publishMessage(id, message);
chatRealtimeService.publishConversationUpdate(id);
}
@MessageExceptionHandler({IllegalArgumentException.class, RuntimeException.class})
@SendToUser("/queue/chat/errors")
public Map<String, Object> handleMessageException(Exception ex, SimpMessageHeaderAccessor headerAccessor) {
return webSocketAuthChannelInterceptor.buildErrorPayload(ex, headerAccessor.getDestination(), headerAccessor.getUser());
}
private User resolveUser(SimpMessageHeaderAccessor headerAccessor) {
Principal principal = headerAccessor.getUser();
if (principal instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticationToken
&& authenticationToken.getPrincipal() instanceof AppPrincipal appPrincipal) {
return userRepository.findById(appPrincipal.getUserId())
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
if (principal instanceof AppPrincipal appPrincipal) {
return userRepository.findById(appPrincipal.getUserId())
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
String tokenHeader = firstHeader(headerAccessor, "Authorization");
if (tokenHeader == null || tokenHeader.isBlank()) {
tokenHeader = firstHeader(headerAccessor, "token");
}
if (tokenHeader == null || tokenHeader.isBlank()) {
throw new IllegalArgumentException("User not authenticated");
}
String token = extractToken(tokenHeader);
Long userId;
try {
userId = jwtUtil.extractUserId(token);
} catch (RuntimeException ex) {
throw new IllegalArgumentException("Invalid websocket token", ex);
}
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
if (user == null) {
throw new IllegalArgumentException("User not found");
}
if (user.getActive() == null || !user.getActive()) {
throw new IllegalArgumentException("User account is inactive");
}
if (!jwtUtil.validateToken(token, user)) {
throw new IllegalArgumentException("Invalid websocket token");
}
return user;
}
private String firstHeader(SimpMessageHeaderAccessor headerAccessor, String name) {
List<String> values = headerAccessor.getNativeHeader(name);
if (values != null && !values.isEmpty()) {
return values.get(0);
}
Map<String, List<String>> headers = headerAccessor.toNativeHeaderMap();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (entry.getKey().equalsIgnoreCase(name)) {
return entry.getValue() == null || entry.getValue().isEmpty() ? null : entry.getValue().get(0);
}
}
return null;
}
private String extractToken(String rawValue) {
String normalized = rawValue.trim();
return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized;
}
}

View File

@@ -0,0 +1,61 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.customer.CustomerRequest;
import com.petshop.backend.dto.customer.CustomerResponse;
import com.petshop.backend.service.CustomerService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/customers")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public class CustomerController {
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
@GetMapping
public ResponseEntity<Page<CustomerResponse>> getAllCustomers(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(customerService.getAllCustomers(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<CustomerResponse> getCustomerById(@PathVariable Long id) {
return ResponseEntity.ok(customerService.getCustomerById(id));
}
@PostMapping
public ResponseEntity<CustomerResponse> createCustomer(@Valid @RequestBody CustomerRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(customerService.createCustomer(request));
}
@PutMapping("/{id}")
public ResponseEntity<CustomerResponse> updateCustomer(
@PathVariable Long id,
@Valid @RequestBody CustomerRequest request) {
return ResponseEntity.ok(customerService.updateCustomer(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteCustomer(@PathVariable Long id) {
customerService.deleteCustomer(id);
return ResponseEntity.noContent().build();
}
@PostMapping("/bulk-delete")
public ResponseEntity<Void> bulkDeleteCustomers(@Valid @RequestBody BulkDeleteRequest request) {
customerService.bulkDeleteCustomers(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,103 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.DropdownOption;
import com.petshop.backend.repository.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/v1/dropdowns")
public class DropdownController {
private final PetRepository petRepository;
private final CustomerRepository customerRepository;
private final ServiceRepository serviceRepository;
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
private final StoreRepository storeRepository;
private final SupplierRepository supplierRepository;
public DropdownController(PetRepository petRepository, CustomerRepository customerRepository,
ServiceRepository serviceRepository, ProductRepository productRepository,
CategoryRepository categoryRepository, StoreRepository storeRepository,
SupplierRepository supplierRepository) {
this.petRepository = petRepository;
this.customerRepository = customerRepository;
this.serviceRepository = serviceRepository;
this.productRepository = productRepository;
this.categoryRepository = categoryRepository;
this.storeRepository = storeRepository;
this.supplierRepository = supplierRepository;
}
@GetMapping("/pets")
public ResponseEntity<List<DropdownOption>> getPets() {
return ResponseEntity.ok(
petRepository.findAll().stream()
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
.collect(Collectors.toList())
);
}
@GetMapping("/customers")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<List<DropdownOption>> getCustomers() {
return ResponseEntity.ok(
customerRepository.findAll().stream()
.map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName()))
.collect(Collectors.toList())
);
}
@GetMapping("/services")
public ResponseEntity<List<DropdownOption>> getServices() {
return ResponseEntity.ok(
serviceRepository.findAll().stream()
.map(s -> new DropdownOption(s.getServiceId(), s.getServiceName()))
.collect(Collectors.toList())
);
}
@GetMapping("/products")
public ResponseEntity<List<DropdownOption>> getProducts() {
return ResponseEntity.ok(
productRepository.findAll().stream()
.map(p -> new DropdownOption(p.getProdId(), p.getProdName()))
.collect(Collectors.toList())
);
}
@GetMapping("/categories")
public ResponseEntity<List<DropdownOption>> getCategories() {
return ResponseEntity.ok(
categoryRepository.findAll().stream()
.map(c -> new DropdownOption(c.getCategoryId(), c.getCategoryName()))
.collect(Collectors.toList())
);
}
@GetMapping("/stores")
public ResponseEntity<List<DropdownOption>> getStores() {
return ResponseEntity.ok(
storeRepository.findAll().stream()
.map(s -> new DropdownOption(s.getStoreId(), s.getStoreName()))
.collect(Collectors.toList())
);
}
@GetMapping("/suppliers")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<DropdownOption>> getSuppliers() {
return ResponseEntity.ok(
supplierRepository.findAll().stream()
.map(s -> new DropdownOption(s.getSupId(), s.getSupCompany()))
.collect(Collectors.toList())
);
}
}

View File

@@ -0,0 +1,49 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.employee.EmployeeRequest;
import com.petshop.backend.dto.employee.EmployeeResponse;
import com.petshop.backend.service.EmployeeService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/employees")
@PreAuthorize("hasRole('ADMIN')")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@GetMapping
public ResponseEntity<Page<EmployeeResponse>> getAllEmployees(@RequestParam(required = false) String q, Pageable pageable) {
return ResponseEntity.ok(employeeService.getAllEmployees(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<EmployeeResponse> getEmployeeById(@PathVariable Long id) {
return ResponseEntity.ok(employeeService.getEmployeeById(id));
}
@PostMapping
public ResponseEntity<EmployeeResponse> createEmployee(@Valid @RequestBody EmployeeRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(employeeService.createEmployee(request));
}
@PutMapping("/{id}")
public ResponseEntity<EmployeeResponse> updateEmployee(@PathVariable Long id, @Valid @RequestBody EmployeeRequest request) {
return ResponseEntity.ok(employeeService.updateEmployee(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
employeeService.deleteEmployee(id);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,18 @@
package com.petshop.backend.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/health")
public class HealthController {
@GetMapping
public ResponseEntity<Map<String, String>> healthCheck() {
return ResponseEntity.ok(Map.of("status", "UP"));
}
}

View File

@@ -0,0 +1,61 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.inventory.InventoryRequest;
import com.petshop.backend.dto.inventory.InventoryResponse;
import com.petshop.backend.service.InventoryService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/inventory")
@PreAuthorize("hasRole('ADMIN')")
public class InventoryController {
private final InventoryService inventoryService;
public InventoryController(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
@GetMapping
public ResponseEntity<Page<InventoryResponse>> getAllInventory(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(inventoryService.getAllInventory(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<InventoryResponse> getInventoryById(@PathVariable Long id) {
return ResponseEntity.ok(inventoryService.getInventoryById(id));
}
@PostMapping
public ResponseEntity<InventoryResponse> createInventory(@Valid @RequestBody InventoryRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(inventoryService.createInventory(request));
}
@PutMapping("/{id}")
public ResponseEntity<InventoryResponse> updateInventory(
@PathVariable Long id,
@Valid @RequestBody InventoryRequest request) {
return ResponseEntity.ok(inventoryService.updateInventory(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteInventory(@PathVariable Long id) {
inventoryService.deleteInventory(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteInventory(@Valid @RequestBody BulkDeleteRequest request) {
inventoryService.bulkDeleteInventory(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.pet.PetRequest;
import com.petshop.backend.dto.pet.PetResponse;
import com.petshop.backend.service.PetService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/pets")
public class PetController {
private final PetService petService;
public PetController(PetService petService) {
this.petService = petService;
}
@GetMapping
public ResponseEntity<Page<PetResponse>> getAllPets(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(petService.getAllPets(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<PetResponse> getPetById(@PathVariable Long id) {
return ResponseEntity.ok(petService.getPetById(id));
}
@PostMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<PetResponse> createPet(@Valid @RequestBody PetRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(petService.createPet(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<PetResponse> updatePet(
@PathVariable Long id,
@Valid @RequestBody PetRequest request) {
return ResponseEntity.ok(petService.updatePet(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> deletePet(@PathVariable Long id) {
petService.deletePet(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) {
petService.bulkDeletePets(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.product.ProductRequest;
import com.petshop.backend.dto.product.ProductResponse;
import com.petshop.backend.service.ProductService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public ResponseEntity<Page<ProductResponse>> getAllProducts(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(productService.getAllProducts(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> getProductById(@PathVariable Long id) {
return ResponseEntity.ok(productService.getProductById(id));
}
@PostMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<ProductResponse> createProduct(@Valid @RequestBody ProductRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(productService.createProduct(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<ProductResponse> updateProduct(
@PathVariable Long id,
@Valid @RequestBody ProductRequest request) {
return ResponseEntity.ok(productService.updateProduct(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) {
productService.bulkDeleteProducts(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,66 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.productsupplier.BulkDeleteProductSupplierRequest;
import com.petshop.backend.dto.productsupplier.ProductSupplierRequest;
import com.petshop.backend.dto.productsupplier.ProductSupplierResponse;
import com.petshop.backend.service.ProductSupplierService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/product-suppliers")
@PreAuthorize("hasRole('ADMIN')")
public class ProductSupplierController {
private final ProductSupplierService productSupplierService;
public ProductSupplierController(ProductSupplierService productSupplierService) {
this.productSupplierService = productSupplierService;
}
@GetMapping
public ResponseEntity<Page<ProductSupplierResponse>> getAllProductSuppliers(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(q, pageable));
}
@GetMapping("/{productId}/{supplierId}")
public ResponseEntity<ProductSupplierResponse> getProductSupplierById(
@PathVariable Long productId,
@PathVariable Long supplierId) {
return ResponseEntity.ok(productSupplierService.getProductSupplierById(productId, supplierId));
}
@PostMapping
public ResponseEntity<ProductSupplierResponse> createProductSupplier(@Valid @RequestBody ProductSupplierRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(productSupplierService.createProductSupplier(request));
}
@PutMapping("/{productId}/{supplierId}")
public ResponseEntity<ProductSupplierResponse> updateProductSupplier(
@PathVariable Long productId,
@PathVariable Long supplierId,
@Valid @RequestBody ProductSupplierRequest request) {
return ResponseEntity.ok(productSupplierService.updateProductSupplier(productId, supplierId, request));
}
@DeleteMapping("/{productId}/{supplierId}")
public ResponseEntity<Void> deleteProductSupplier(
@PathVariable Long productId,
@PathVariable Long supplierId) {
productSupplierService.deleteProductSupplier(productId, supplierId);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteProductSuppliers(@Valid @RequestBody BulkDeleteProductSupplierRequest request) {
productSupplierService.bulkDeleteProductSuppliers(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,33 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse;
import com.petshop.backend.service.PurchaseOrderService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/purchase-orders")
@PreAuthorize("hasRole('ADMIN')")
public class PurchaseOrderController {
private final PurchaseOrderService purchaseOrderService;
public PurchaseOrderController(PurchaseOrderService purchaseOrderService) {
this.purchaseOrderService = purchaseOrderService;
}
@GetMapping
public ResponseEntity<Page<PurchaseOrderResponse>> getAllPurchaseOrders(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<PurchaseOrderResponse> getPurchaseOrderById(@PathVariable Long id) {
return ResponseEntity.ok(purchaseOrderService.getPurchaseOrderById(id));
}
}

View File

@@ -0,0 +1,133 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.refund.RefundRequest;
import com.petshop.backend.dto.refund.RefundResponse;
import com.petshop.backend.dto.refund.RefundUpdateRequest;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.RefundService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/refunds")
public class RefundController {
private final RefundService refundService;
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public RefundController(RefundService refundService, UserRepository userRepository, CustomerRepository customerRepository) {
this.refundService = refundService;
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@PostMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<?> createRefund(@Valid @RequestBody RefundRequest request) {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
RefundResponse refund = refundService.createRefund(request, customerId);
return ResponseEntity.status(HttpStatus.CREATED).body(refund);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.badRequest().body(error);
}
}
@GetMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<List<RefundResponse>> getAllRefunds() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
List<RefundResponse> refunds = refundService.getAllRefunds(customerId);
return ResponseEntity.ok(refunds);
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<?> getRefundById(@PathVariable Long id) {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String role = authentication.getAuthorities().stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse(null);
Long customerId = null;
if (role != null && role.equals("CUSTOMER")) {
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
customerId = customer.getCustomerId();
}
RefundResponse refund = refundService.getRefundById(id, customerId);
return ResponseEntity.ok(refund);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<?> updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) {
try {
RefundResponse refund = refundService.updateRefundStatus(id, request.getStatus());
return ResponseEntity.ok(refund);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.badRequest().body(error);
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteRefund(@PathVariable Long id) {
try {
refundService.deleteRefund(id);
Map<String, String> response = new HashMap<>();
response.put("message", "Refund deleted successfully");
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
}

View File

@@ -0,0 +1,43 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.sale.SaleRequest;
import com.petshop.backend.dto.sale.SaleResponse;
import com.petshop.backend.service.SaleService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/sales")
public class SaleController {
private final SaleService saleService;
public SaleController(SaleService saleService) {
this.saleService = saleService;
}
@GetMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Page<SaleResponse>> getAllSales(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(saleService.getAllSales(q, pageable));
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<SaleResponse> getSaleById(@PathVariable Long id) {
return ResponseEntity.ok(saleService.getSaleById(id));
}
@PostMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<SaleResponse> createSale(@Valid @RequestBody SaleRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(saleService.createSale(request));
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.service.ServiceRequest;
import com.petshop.backend.dto.service.ServiceResponse;
import com.petshop.backend.service.ServiceService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/services")
public class ServiceController {
private final ServiceService serviceService;
public ServiceController(ServiceService serviceService) {
this.serviceService = serviceService;
}
@GetMapping
public ResponseEntity<Page<ServiceResponse>> getAllServices(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(serviceService.getAllServices(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<ServiceResponse> getServiceById(@PathVariable Long id) {
return ResponseEntity.ok(serviceService.getServiceById(id));
}
@PostMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<ServiceResponse> createService(@Valid @RequestBody ServiceRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(serviceService.createService(request));
}
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<ServiceResponse> updateService(
@PathVariable Long id,
@Valid @RequestBody ServiceRequest request) {
return ResponseEntity.ok(serviceService.updateService(id, request));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> deleteService(@PathVariable Long id) {
serviceService.deleteService(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Void> bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) {
serviceService.bulkDeleteServices(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,61 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.store.StoreRequest;
import com.petshop.backend.dto.store.StoreResponse;
import com.petshop.backend.service.StoreService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/stores")
@PreAuthorize("hasRole('ADMIN')")
public class StoreController {
private final StoreService storeService;
public StoreController(StoreService storeService) {
this.storeService = storeService;
}
@GetMapping
public ResponseEntity<Page<StoreResponse>> getAllStores(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(storeService.getAllStores(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<StoreResponse> getStoreById(@PathVariable Long id) {
return ResponseEntity.ok(storeService.getStoreById(id));
}
@PostMapping
public ResponseEntity<StoreResponse> createStore(@Valid @RequestBody StoreRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(storeService.createStore(request));
}
@PutMapping("/{id}")
public ResponseEntity<StoreResponse> updateStore(
@PathVariable Long id,
@Valid @RequestBody StoreRequest request) {
return ResponseEntity.ok(storeService.updateStore(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStore(@PathVariable Long id) {
storeService.deleteStore(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteStores(@Valid @RequestBody BulkDeleteRequest request) {
storeService.bulkDeleteStores(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,61 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.supplier.SupplierRequest;
import com.petshop.backend.dto.supplier.SupplierResponse;
import com.petshop.backend.service.SupplierService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/suppliers")
@PreAuthorize("hasRole('ADMIN')")
public class SupplierController {
private final SupplierService supplierService;
public SupplierController(SupplierService supplierService) {
this.supplierService = supplierService;
}
@GetMapping
public ResponseEntity<Page<SupplierResponse>> getAllSuppliers(
@RequestParam(required = false) String q,
Pageable pageable) {
return ResponseEntity.ok(supplierService.getAllSuppliers(q, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<SupplierResponse> getSupplierById(@PathVariable Long id) {
return ResponseEntity.ok(supplierService.getSupplierById(id));
}
@PostMapping
public ResponseEntity<SupplierResponse> createSupplier(@Valid @RequestBody SupplierRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(supplierService.createSupplier(request));
}
@PutMapping("/{id}")
public ResponseEntity<SupplierResponse> updateSupplier(
@PathVariable Long id,
@Valid @RequestBody SupplierRequest request) {
return ResponseEntity.ok(supplierService.updateSupplier(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteSupplier(@PathVariable Long id) {
supplierService.deleteSupplier(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteSuppliers(@Valid @RequestBody BulkDeleteRequest request) {
supplierService.bulkDeleteSuppliers(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,63 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.user.UserRequest;
import com.petshop.backend.dto.user.UserResponse;
import com.petshop.backend.entity.User;
import com.petshop.backend.service.UserService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/users")
@PreAuthorize("hasRole('ADMIN')")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<Page<UserResponse>> getAllUsers(
@RequestParam(required = false) String q,
@RequestParam(required = false) String role,
Pageable pageable) {
return ResponseEntity.ok(userService.getAllUsers(q, role, pageable));
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody UserRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(request));
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok(userService.updateUser(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
public ResponseEntity<Void> bulkDeleteUsers(@Valid @RequestBody BulkDeleteRequest request) {
userService.bulkDeleteUsers(request);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,78 @@
package com.petshop.backend.dto.adoption;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.Objects;
public class AdoptionRequest {
@NotNull(message = "Pet ID is required")
private Long petId;
@NotNull(message = "Customer ID is required")
private Long customerId;
@NotNull(message = "Adoption date is required")
private LocalDate adoptionDate;
@NotBlank(message = "Adoption status is required")
private String adoptionStatus;
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(LocalDate adoptionDate) {
this.adoptionDate = adoptionDate;
}
public String getAdoptionStatus() {
return adoptionStatus;
}
public void setAdoptionStatus(String adoptionStatus) {
this.adoptionStatus = adoptionStatus;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AdoptionRequest that = (AdoptionRequest) o;
return Objects.equals(petId, that.petId) &&
Objects.equals(customerId, that.customerId) &&
Objects.equals(adoptionDate, that.adoptionDate) &&
Objects.equals(adoptionStatus, that.adoptionStatus);
}
@Override
public int hashCode() {
return Objects.hash(petId, customerId, adoptionDate, adoptionStatus);
}
@Override
public String toString() {
return "AdoptionRequest{" +
"petId=" + petId +
", customerId=" + customerId +
", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' +
'}';
}
}

View File

@@ -0,0 +1,144 @@
package com.petshop.backend.dto.adoption;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
public class AdoptionResponse {
private Long adoptionId;
private Long petId;
private String petName;
private Long customerId;
private String customerName;
private LocalDate adoptionDate;
private String adoptionStatus;
private BigDecimal adoptionFee;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AdoptionResponse() {
}
public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.adoptionId = adoptionId;
this.petId = petId;
this.petName = petName;
this.customerId = customerId;
this.customerName = customerName;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
this.adoptionFee = adoptionFee;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getAdoptionId() {
return adoptionId;
}
public void setAdoptionId(Long adoptionId) {
this.adoptionId = adoptionId;
}
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(LocalDate adoptionDate) {
this.adoptionDate = adoptionDate;
}
public String getAdoptionStatus() {
return adoptionStatus;
}
public void setAdoptionStatus(String adoptionStatus) {
this.adoptionStatus = adoptionStatus;
}
public BigDecimal getAdoptionFee() {
return adoptionFee;
}
public void setAdoptionFee(BigDecimal adoptionFee) {
this.adoptionFee = adoptionFee;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AdoptionResponse that = (AdoptionResponse) o;
return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(adoptionId, petId, petName, customerId, customerName, adoptionDate, adoptionStatus, adoptionFee, createdAt, updatedAt);
}
@Override
public String toString() {
return "AdoptionResponse{" +
"adoptionId=" + adoptionId +
", petId=" + petId +
", petName='" + petName + '\'' +
", customerId=" + customerId +
", customerName='" + customerName + '\'' +
", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' +
", adoptionFee=" + adoptionFee +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,343 @@
package com.petshop.backend.dto.analytics;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
public class DashboardResponse {
private SalesSummary salesSummary;
private InventorySummary inventorySummary;
private List<TopProduct> topProducts;
private List<DailySales> dailySales;
public DashboardResponse() {
}
public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List<TopProduct> topProducts, List<DailySales> dailySales) {
this.salesSummary = salesSummary;
this.inventorySummary = inventorySummary;
this.topProducts = topProducts;
this.dailySales = dailySales;
}
public SalesSummary getSalesSummary() {
return salesSummary;
}
public void setSalesSummary(SalesSummary salesSummary) {
this.salesSummary = salesSummary;
}
public InventorySummary getInventorySummary() {
return inventorySummary;
}
public void setInventorySummary(InventorySummary inventorySummary) {
this.inventorySummary = inventorySummary;
}
public List<TopProduct> getTopProducts() {
return topProducts;
}
public void setTopProducts(List<TopProduct> topProducts) {
this.topProducts = topProducts;
}
public List<DailySales> getDailySales() {
return dailySales;
}
public void setDailySales(List<DailySales> dailySales) {
this.dailySales = dailySales;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DashboardResponse that = (DashboardResponse) o;
return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales);
}
@Override
public int hashCode() {
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales);
}
@Override
public String toString() {
return "DashboardResponse{" +
"salesSummary=" + salesSummary +
", inventorySummary=" + inventorySummary +
", topProducts=" + topProducts +
", dailySales=" + dailySales +
'}';
}
public static class SalesSummary {
private BigDecimal totalRevenue;
private Long totalSales;
private BigDecimal totalRefunds;
private Long totalRefundCount;
public SalesSummary() {
}
public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount) {
this.totalRevenue = totalRevenue;
this.totalSales = totalSales;
this.totalRefunds = totalRefunds;
this.totalRefundCount = totalRefundCount;
}
public BigDecimal getTotalRevenue() {
return totalRevenue;
}
public void setTotalRevenue(BigDecimal totalRevenue) {
this.totalRevenue = totalRevenue;
}
public Long getTotalSales() {
return totalSales;
}
public void setTotalSales(Long totalSales) {
this.totalSales = totalSales;
}
public BigDecimal getTotalRefunds() {
return totalRefunds;
}
public void setTotalRefunds(BigDecimal totalRefunds) {
this.totalRefunds = totalRefunds;
}
public Long getTotalRefundCount() {
return totalRefundCount;
}
public void setTotalRefundCount(Long totalRefundCount) {
this.totalRefundCount = totalRefundCount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SalesSummary that = (SalesSummary) o;
return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount);
}
@Override
public int hashCode() {
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount);
}
@Override
public String toString() {
return "SalesSummary{" +
"totalRevenue=" + totalRevenue +
", totalSales=" + totalSales +
", totalRefunds=" + totalRefunds +
", totalRefundCount=" + totalRefundCount +
'}';
}
}
public static class InventorySummary {
private Long totalProducts;
private Long lowStockProducts;
private Long outOfStockProducts;
public InventorySummary() {
}
public InventorySummary(Long totalProducts, Long lowStockProducts, Long outOfStockProducts) {
this.totalProducts = totalProducts;
this.lowStockProducts = lowStockProducts;
this.outOfStockProducts = outOfStockProducts;
}
public Long getTotalProducts() {
return totalProducts;
}
public void setTotalProducts(Long totalProducts) {
this.totalProducts = totalProducts;
}
public Long getLowStockProducts() {
return lowStockProducts;
}
public void setLowStockProducts(Long lowStockProducts) {
this.lowStockProducts = lowStockProducts;
}
public Long getOutOfStockProducts() {
return outOfStockProducts;
}
public void setOutOfStockProducts(Long outOfStockProducts) {
this.outOfStockProducts = outOfStockProducts;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InventorySummary that = (InventorySummary) o;
return Objects.equals(totalProducts, that.totalProducts) && Objects.equals(lowStockProducts, that.lowStockProducts) && Objects.equals(outOfStockProducts, that.outOfStockProducts);
}
@Override
public int hashCode() {
return Objects.hash(totalProducts, lowStockProducts, outOfStockProducts);
}
@Override
public String toString() {
return "InventorySummary{" +
"totalProducts=" + totalProducts +
", lowStockProducts=" + lowStockProducts +
", outOfStockProducts=" + outOfStockProducts +
'}';
}
}
public static class TopProduct {
private Long productId;
private String productName;
private Long quantitySold;
private BigDecimal revenue;
public TopProduct() {
}
public TopProduct(Long productId, String productName, Long quantitySold, BigDecimal revenue) {
this.productId = productId;
this.productName = productName;
this.quantitySold = quantitySold;
this.revenue = revenue;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Long getQuantitySold() {
return quantitySold;
}
public void setQuantitySold(Long quantitySold) {
this.quantitySold = quantitySold;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TopProduct that = (TopProduct) o;
return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantitySold, that.quantitySold) && Objects.equals(revenue, that.revenue);
}
@Override
public int hashCode() {
return Objects.hash(productId, productName, quantitySold, revenue);
}
@Override
public String toString() {
return "TopProduct{" +
"productId=" + productId +
", productName='" + productName + '\'' +
", quantitySold=" + quantitySold +
", revenue=" + revenue +
'}';
}
}
public static class DailySales {
private String date;
private BigDecimal revenue;
private Long salesCount;
public DailySales() {
}
public DailySales(String date, BigDecimal revenue, Long salesCount) {
this.date = date;
this.revenue = revenue;
this.salesCount = salesCount;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
public Long getSalesCount() {
return salesCount;
}
public void setSalesCount(Long salesCount) {
this.salesCount = salesCount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DailySales that = (DailySales) o;
return Objects.equals(date, that.date) && Objects.equals(revenue, that.revenue) && Objects.equals(salesCount, that.salesCount);
}
@Override
public int hashCode() {
return Objects.hash(date, revenue, salesCount);
}
@Override
public String toString() {
return "DailySales{" +
"date='" + date + '\'' +
", revenue=" + revenue +
", salesCount=" + salesCount +
'}';
}
}
}

View File

@@ -0,0 +1,119 @@
package com.petshop.backend.dto.appointment;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;
public class AppointmentRequest {
@NotNull(message = "Customer ID is required")
private Long customerId;
@NotNull(message = "Store ID is required")
private Long storeId;
@NotNull(message = "Service ID is required")
private Long serviceId;
@NotNull(message = "Appointment date is required")
private LocalDate appointmentDate;
@NotNull(message = "Appointment time is required")
private LocalTime appointmentTime;
@NotNull(message = "Appointment status is required")
private String appointmentStatus;
@NotEmpty(message = "At least one pet must be specified")
private List<Long> petIds;
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public String getAppointmentStatus() {
return appointmentStatus;
}
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
public List<Long> getPetIds() {
return petIds;
}
public void setPetIds(List<Long> petIds) {
this.petIds = petIds;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppointmentRequest that = (AppointmentRequest) o;
return Objects.equals(customerId, that.customerId) &&
Objects.equals(storeId, that.storeId) &&
Objects.equals(serviceId, that.serviceId) &&
Objects.equals(appointmentDate, that.appointmentDate) &&
Objects.equals(appointmentTime, that.appointmentTime) &&
Objects.equals(appointmentStatus, that.appointmentStatus) &&
Objects.equals(petIds, that.petIds);
}
@Override
public int hashCode() {
return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds);
}
@Override
public String toString() {
return "AppointmentRequest{" +
"customerId=" + customerId +
", storeId=" + storeId +
", serviceId=" + serviceId +
", appointmentDate=" + appointmentDate +
", appointmentTime=" + appointmentTime +
", appointmentStatus='" + appointmentStatus + '\'' +
", petIds=" + petIds +
'}';
}
}

View File

@@ -0,0 +1,189 @@
package com.petshop.backend.dto.appointment;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;
public class AppointmentResponse {
private Long appointmentId;
private Long customerId;
private String customerName;
private Long storeId;
private String storeName;
private Long serviceId;
private String serviceName;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
private List<String> petNames;
private List<Long> petIds;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AppointmentResponse() {
}
public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List<String> petNames, List<Long> petIds, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.appointmentId = appointmentId;
this.customerId = customerId;
this.customerName = customerName;
this.storeId = storeId;
this.storeName = storeName;
this.serviceId = serviceId;
this.serviceName = serviceName;
this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime;
this.appointmentStatus = appointmentStatus;
this.petNames = petNames;
this.petIds = petIds;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getAppointmentId() {
return appointmentId;
}
public void setAppointmentId(Long appointmentId) {
this.appointmentId = appointmentId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public String getAppointmentStatus() {
return appointmentStatus;
}
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
public List<String> getPetNames() {
return petNames;
}
public void setPetNames(List<String> petNames) {
this.petNames = petNames;
}
public List<Long> getPetIds() {
return petIds;
}
public void setPetIds(List<Long> petIds) {
this.petIds = petIds;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppointmentResponse that = (AppointmentResponse) o;
return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt);
}
@Override
public String toString() {
return "AppointmentResponse{" +
"appointmentId=" + appointmentId +
", customerId=" + customerId +
", customerName='" + customerName + '\'' +
", storeId=" + storeId +
", storeName='" + storeName + '\'' +
", serviceId=" + serviceId +
", serviceName='" + serviceName + '\'' +
", appointmentDate=" + appointmentDate +
", appointmentTime=" + appointmentTime +
", appointmentStatus='" + appointmentStatus + '\'' +
", petNames=" + petNames +
", petIds=" + petIds +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,54 @@
package com.petshop.backend.dto.auth;
import java.util.Objects;
public class AvatarUploadResponse {
private String avatarUrl;
private String message;
public AvatarUploadResponse() {
}
public AvatarUploadResponse(String avatarUrl, String message) {
this.avatarUrl = avatarUrl;
this.message = message;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AvatarUploadResponse that = (AvatarUploadResponse) o;
return Objects.equals(avatarUrl, that.avatarUrl) &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(avatarUrl, message);
}
@Override
public String toString() {
return "AvatarUploadResponse{" +
"avatarUrl='" + avatarUrl + '\'' +
", message='" + message + '\'' +
'}';
}
}

View File

@@ -0,0 +1,50 @@
package com.petshop.backend.dto.auth;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class LoginRequest {
@NotBlank(message = "Username is required")
private String username;
@NotBlank(message = "Password is required")
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoginRequest that = (LoginRequest) o;
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password);
}
@Override
public int hashCode() {
return Objects.hash(username, password);
}
@Override
public String toString() {
return "LoginRequest{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.dto.auth;
import java.util.Objects;
public class LoginResponse {
private String token;
private String username;
private String role;
public LoginResponse() {
}
public LoginResponse(String token, String username, String role) {
this.token = token;
this.username = username;
this.role = role;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoginResponse that = (LoginResponse) o;
return Objects.equals(token, that.token) && Objects.equals(username, that.username) && Objects.equals(role, that.role);
}
@Override
public int hashCode() {
return Objects.hash(token, username, role);
}
@Override
public String toString() {
return "LoginResponse{" +
"token='" + token + '\'' +
", username='" + username + '\'' +
", role='" + role + '\'' +
'}';
}
}

View File

@@ -0,0 +1,90 @@
package com.petshop.backend.dto.auth;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Size;
import java.util.Objects;
public class ProfileUpdateRequest {
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@Email(message = "Email must be valid")
private String email;
@Size(max = 100, message = "Full name must not exceed 100 characters")
private String fullName;
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProfileUpdateRequest that = (ProfileUpdateRequest) o;
return Objects.equals(username, that.username) &&
Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone) &&
Objects.equals(password, that.password);
}
@Override
public int hashCode() {
return Objects.hash(username, email, fullName, phone, password);
}
@Override
public String toString() {
return "ProfileUpdateRequest{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", password='" + password + '\'' +
'}';
}
}

View File

@@ -0,0 +1,96 @@
package com.petshop.backend.dto.auth;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.Objects;
public class RegisterRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@NotBlank(message = "Full name is required")
@Size(max = 100, message = "Full name must not exceed 100 characters")
private String fullName;
@NotBlank(message = "Phone is required")
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegisterRequest that = (RegisterRequest) o;
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone);
}
@Override
public int hashCode() {
return Objects.hash(username, password, email, fullName, phone);
}
@Override
public String toString() {
return "RegisterRequest{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
'}';
}
}

View File

@@ -0,0 +1,102 @@
package com.petshop.backend.dto.auth;
import java.util.Objects;
public class RegisterResponse {
private Long id;
private String username;
private String email;
private String phone;
private String role;
private String token;
public RegisterResponse() {
}
public RegisterResponse(Long id, String username, String email, String phone, String role, String token) {
this.id = id;
this.username = username;
this.email = email;
this.phone = phone;
this.role = role;
this.token = token;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegisterResponse that = (RegisterResponse) o;
return Objects.equals(id, that.id) &&
Objects.equals(username, that.username) &&
Objects.equals(email, that.email) &&
Objects.equals(phone, that.phone) &&
Objects.equals(role, that.role) &&
Objects.equals(token, that.token);
}
@Override
public int hashCode() {
return Objects.hash(id, username, email, phone, role, token);
}
@Override
public String toString() {
return "RegisterResponse{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role='" + role + '\'' +
", token='" + token + '\'' +
'}';
}
}

View File

@@ -0,0 +1,138 @@
package com.petshop.backend.dto.auth;
import java.util.Objects;
public class UserInfoResponse {
private Long id;
private String username;
private String email;
private String fullName;
private String phone;
private String avatarUrl;
private String role;
private Long storeId;
private String storeName;
public UserInfoResponse() {
}
public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long storeId, String storeName) {
this.id = id;
this.username = username;
this.email = email;
this.fullName = fullName;
this.phone = phone;
this.avatarUrl = avatarUrl;
this.role = role;
this.storeId = storeId;
this.storeName = storeName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserInfoResponse that = (UserInfoResponse) o;
return Objects.equals(id, that.id) &&
Objects.equals(username, that.username) &&
Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone) &&
Objects.equals(avatarUrl, that.avatarUrl) &&
Objects.equals(role, that.role) &&
Objects.equals(storeId, that.storeId) &&
Objects.equals(storeName, that.storeName);
}
@Override
public int hashCode() {
return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, storeId, storeName);
}
@Override
public String toString() {
return "UserInfoResponse{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", avatarUrl='" + avatarUrl + '\'' +
", role='" + role + '\'' +
", storeId=" + storeId +
", storeName='" + storeName + '\'' +
'}';
}
}

View File

@@ -0,0 +1,49 @@
package com.petshop.backend.dto.category;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class CategoryRequest {
@NotBlank(message = "Category name is required")
private String categoryName;
private String categoryType;
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryType() {
return categoryType;
}
public void setCategoryType(String categoryType) {
this.categoryType = categoryType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CategoryRequest that = (CategoryRequest) o;
return Objects.equals(categoryName, that.categoryName) &&
Objects.equals(categoryType, that.categoryType);
}
@Override
public int hashCode() {
return Objects.hash(categoryName, categoryType);
}
@Override
public String toString() {
return "CategoryRequest{" +
"categoryName='" + categoryName + '\'' +
", categoryType='" + categoryType + '\'' +
'}';
}
}

View File

@@ -0,0 +1,87 @@
package com.petshop.backend.dto.category;
import java.time.LocalDateTime;
import java.util.Objects;
public class CategoryResponse {
private Long categoryId;
private String categoryName;
private String categoryType;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public CategoryResponse() {
}
public CategoryResponse(Long categoryId, String categoryName, String categoryType, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.categoryId = categoryId;
this.categoryName = categoryName;
this.categoryType = categoryType;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryType() {
return categoryType;
}
public void setCategoryType(String categoryType) {
this.categoryType = categoryType;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CategoryResponse that = (CategoryResponse) o;
return Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(categoryType, that.categoryType) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(categoryId, categoryName, categoryType, createdAt, updatedAt);
}
@Override
public String toString() {
return "CategoryResponse{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", categoryType='" + categoryType + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,23 @@
package com.petshop.backend.dto.chat;
import jakarta.validation.constraints.NotBlank;
public class ConversationRequest {
@NotBlank(message = "Initial message is required")
private String message;
public ConversationRequest() {
}
public ConversationRequest(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,118 @@
package com.petshop.backend.dto.chat;
import com.petshop.backend.entity.Conversation;
import java.time.LocalDateTime;
public class ConversationResponse {
private Long id;
private Long customerId;
private Long staffId;
private String status;
private String mode;
private String lastMessage;
private LocalDateTime humanRequestedAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public ConversationResponse() {
}
public ConversationResponse(Long id, Long customerId, Long staffId, String status, String mode, String lastMessage, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.customerId = customerId;
this.staffId = staffId;
this.status = status;
this.mode = mode;
this.lastMessage = lastMessage;
this.humanRequestedAt = humanRequestedAt;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public static ConversationResponse fromEntity(Conversation conversation, String lastMessage) {
ConversationResponse response = new ConversationResponse();
response.setId(conversation.getId());
response.setCustomerId(conversation.getCustomerId());
response.setStaffId(conversation.getStaffId());
response.setStatus(conversation.getStatus().name());
response.setMode(conversation.getMode().name());
response.setLastMessage(lastMessage);
response.setHumanRequestedAt(conversation.getHumanRequestedAt());
response.setCreatedAt(conversation.getCreatedAt());
response.setUpdatedAt(conversation.getUpdatedAt());
return response;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getStaffId() {
return staffId;
}
public void setStaffId(Long staffId) {
this.staffId = staffId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public String getLastMessage() {
return lastMessage;
}
public void setLastMessage(String lastMessage) {
this.lastMessage = lastMessage;
}
public LocalDateTime getHumanRequestedAt() {
return humanRequestedAt;
}
public void setHumanRequestedAt(LocalDateTime humanRequestedAt) {
this.humanRequestedAt = humanRequestedAt;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,23 @@
package com.petshop.backend.dto.chat;
import jakarta.validation.constraints.NotBlank;
public class MessageRequest {
@NotBlank(message = "Message content is required")
private String content;
public MessageRequest() {
}
public MessageRequest(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@@ -0,0 +1,85 @@
package com.petshop.backend.dto.chat;
import com.petshop.backend.entity.Message;
import java.time.LocalDateTime;
public class MessageResponse {
private Long id;
private Long conversationId;
private Long senderId;
private String content;
private LocalDateTime timestamp;
private Boolean isRead;
public MessageResponse() {
}
public MessageResponse(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) {
this.id = id;
this.conversationId = conversationId;
this.senderId = senderId;
this.content = content;
this.timestamp = timestamp;
this.isRead = isRead;
}
public static MessageResponse fromEntity(Message message) {
MessageResponse response = new MessageResponse();
response.setId(message.getId());
response.setConversationId(message.getConversationId());
response.setSenderId(message.getSenderId());
response.setContent(message.getContent());
response.setTimestamp(message.getTimestamp());
response.setIsRead(message.getIsRead());
return response;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getConversationId() {
return conversationId;
}
public void setConversationId(Long conversationId) {
this.conversationId = conversationId;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public Boolean getIsRead() {
return isRead;
}
public void setIsRead(Boolean isRead) {
this.isRead = isRead;
}
}

View File

@@ -0,0 +1,38 @@
package com.petshop.backend.dto.common;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Objects;
public class BulkDeleteRequest {
@NotEmpty(message = "IDs list cannot be empty")
private List<Long> ids;
public List<Long> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
this.ids = ids;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BulkDeleteRequest that = (BulkDeleteRequest) o;
return Objects.equals(ids, that.ids);
}
@Override
public int hashCode() {
return Objects.hash(ids);
}
@Override
public String toString() {
return "BulkDeleteRequest{" +
"ids=" + ids +
'}';
}
}

View File

@@ -0,0 +1,53 @@
package com.petshop.backend.dto.common;
import java.util.Objects;
public class DropdownOption {
private Long id;
private String label;
public DropdownOption() {
}
public DropdownOption(Long id, String label) {
this.id = id;
this.label = label;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DropdownOption that = (DropdownOption) o;
return Objects.equals(id, that.id) && Objects.equals(label, that.label);
}
@Override
public int hashCode() {
return Objects.hash(id, label);
}
@Override
public String toString() {
return "DropdownOption{" +
"id=" + id +
", label='" + label + '\'' +
'}';
}
}

View File

@@ -0,0 +1,64 @@
package com.petshop.backend.dto.customer;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class CustomerRequest {
@NotBlank(message = "First name is required")
private String firstName;
@NotBlank(message = "Last name is required")
private String lastName;
@Email(message = "Invalid email format")
private String email;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomerRequest that = (CustomerRequest) o;
return Objects.equals(firstName, that.firstName) &&
Objects.equals(lastName, that.lastName) &&
Objects.equals(email, that.email);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName, email);
}
@Override
public String toString() {
return "CustomerRequest{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
'}';
}
}

View File

@@ -0,0 +1,98 @@
package com.petshop.backend.dto.customer;
import java.time.LocalDateTime;
import java.util.Objects;
public class CustomerResponse {
private Long customerId;
private String firstName;
private String lastName;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public CustomerResponse() {
}
public CustomerResponse(Long customerId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.customerId = customerId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomerResponse that = (CustomerResponse) o;
return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(customerId, firstName, lastName, email, createdAt, updatedAt);
}
@Override
public String toString() {
return "CustomerResponse{" +
"customerId=" + customerId +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,51 @@
package com.petshop.backend.dto.employee;
import com.petshop.backend.entity.User;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class EmployeeRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
@NotBlank(message = "First name is required")
private String firstName;
@NotBlank(message = "Last name is required")
private String lastName;
@Email(message = "Invalid email format")
private String email;
@NotBlank(message = "Phone is required")
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
@NotNull(message = "Role is required")
private User.Role role;
private Boolean active = true;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public User.Role getRole() { return role; }
public void setRole(User.Role role) { this.role = role; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
}

View File

@@ -0,0 +1,43 @@
package com.petshop.backend.dto.employee;
import java.time.LocalDateTime;
public class EmployeeResponse {
private Long employeeId;
private Long userId;
private String username;
private String firstName;
private String lastName;
private String fullName;
private String email;
private String phone;
private String role;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getEmployeeId() { return employeeId; }
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -0,0 +1,52 @@
package com.petshop.backend.dto.inventory;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import java.util.Objects;
public class InventoryRequest {
@NotNull(message = "Product ID is required")
private Long prodId;
@NotNull(message = "Quantity is required")
@PositiveOrZero(message = "Quantity must be zero or positive")
private Integer quantity;
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InventoryRequest that = (InventoryRequest) o;
return Objects.equals(prodId, that.prodId) &&
Objects.equals(quantity, that.quantity);
}
@Override
public int hashCode() {
return Objects.hash(prodId, quantity);
}
@Override
public String toString() {
return "InventoryRequest{" +
"prodId=" + prodId +
", quantity=" + quantity +
'}';
}
}

View File

@@ -0,0 +1,109 @@
package com.petshop.backend.dto.inventory;
import java.time.LocalDateTime;
import java.util.Objects;
public class InventoryResponse {
private Long inventoryId;
private Long prodId;
private String productName;
private String categoryName;
private Integer quantity;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public InventoryResponse() {
}
public InventoryResponse(Long inventoryId, Long prodId, String productName, String categoryName, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.inventoryId = inventoryId;
this.prodId = prodId;
this.productName = productName;
this.categoryName = categoryName;
this.quantity = quantity;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getInventoryId() {
return inventoryId;
}
public void setInventoryId(Long inventoryId) {
this.inventoryId = inventoryId;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InventoryResponse that = (InventoryResponse) o;
return Objects.equals(inventoryId, that.inventoryId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(quantity, that.quantity) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(inventoryId, prodId, productName, categoryName, quantity, createdAt, updatedAt);
}
@Override
public String toString() {
return "InventoryResponse{" +
"inventoryId=" + inventoryId +
", prodId=" + prodId +
", productName='" + productName + '\'' +
", categoryName='" + categoryName + '\'' +
", quantity=" + quantity +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,103 @@
package com.petshop.backend.dto.pet;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.util.Objects;
public class PetRequest {
@NotBlank(message = "Pet name is required")
private String petName;
@NotBlank(message = "Species is required")
private String petSpecies;
private String petBreed;
@Positive(message = "Age must be positive")
private Integer petAge;
@NotNull(message = "Status is required")
private String petStatus;
private BigDecimal petPrice;
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getPetSpecies() {
return petSpecies;
}
public void setPetSpecies(String petSpecies) {
this.petSpecies = petSpecies;
}
public String getPetBreed() {
return petBreed;
}
public void setPetBreed(String petBreed) {
this.petBreed = petBreed;
}
public Integer getPetAge() {
return petAge;
}
public void setPetAge(Integer petAge) {
this.petAge = petAge;
}
public String getPetStatus() {
return petStatus;
}
public void setPetStatus(String petStatus) {
this.petStatus = petStatus;
}
public BigDecimal getPetPrice() {
return petPrice;
}
public void setPetPrice(BigDecimal petPrice) {
this.petPrice = petPrice;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PetRequest that = (PetRequest) o;
return Objects.equals(petName, that.petName) &&
Objects.equals(petSpecies, that.petSpecies) &&
Objects.equals(petBreed, that.petBreed) &&
Objects.equals(petAge, that.petAge) &&
petStatus == that.petStatus &&
Objects.equals(petPrice, that.petPrice);
}
@Override
public int hashCode() {
return Objects.hash(petName, petSpecies, petBreed, petAge, petStatus, petPrice);
}
@Override
public String toString() {
return "PetRequest{" +
"petName='" + petName + '\'' +
", petSpecies='" + petSpecies + '\'' +
", petBreed='" + petBreed + '\'' +
", petAge=" + petAge +
", petStatus=" + petStatus +
", petPrice=" + petPrice +
'}';
}
}

View File

@@ -0,0 +1,132 @@
package com.petshop.backend.dto.pet;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
public class PetResponse {
private Long petId;
private String petName;
private String petSpecies;
private String petBreed;
private Integer petAge;
private String petStatus;
private BigDecimal petPrice;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public PetResponse() {
}
public PetResponse(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.petId = petId;
this.petName = petName;
this.petSpecies = petSpecies;
this.petBreed = petBreed;
this.petAge = petAge;
this.petStatus = petStatus;
this.petPrice = petPrice;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getPetSpecies() {
return petSpecies;
}
public void setPetSpecies(String petSpecies) {
this.petSpecies = petSpecies;
}
public String getPetBreed() {
return petBreed;
}
public void setPetBreed(String petBreed) {
this.petBreed = petBreed;
}
public Integer getPetAge() {
return petAge;
}
public void setPetAge(Integer petAge) {
this.petAge = petAge;
}
public String getPetStatus() {
return petStatus;
}
public void setPetStatus(String petStatus) {
this.petStatus = petStatus;
}
public BigDecimal getPetPrice() {
return petPrice;
}
public void setPetPrice(BigDecimal petPrice) {
this.petPrice = petPrice;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PetResponse that = (PetResponse) o;
return Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, createdAt, updatedAt);
}
@Override
public String toString() {
return "PetResponse{" +
"petId=" + petId +
", petName='" + petName + '\'' +
", petSpecies='" + petSpecies + '\'' +
", petBreed='" + petBreed + '\'' +
", petAge=" + petAge +
", petStatus='" + petStatus + '\'' +
", petPrice=" + petPrice +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,79 @@
package com.petshop.backend.dto.product;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.util.Objects;
public class ProductRequest {
@NotBlank(message = "Product name is required")
private String prodName;
@NotNull(message = "Category ID is required")
private Long categoryId;
private String prodDesc;
@NotNull(message = "Product price is required")
@Positive(message = "Price must be positive")
private BigDecimal prodPrice;
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getProdDesc() {
return prodDesc;
}
public void setProdDesc(String prodDesc) {
this.prodDesc = prodDesc;
}
public BigDecimal getProdPrice() {
return prodPrice;
}
public void setProdPrice(BigDecimal prodPrice) {
this.prodPrice = prodPrice;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductRequest that = (ProductRequest) o;
return Objects.equals(prodName, that.prodName) &&
Objects.equals(categoryId, that.categoryId) &&
Objects.equals(prodDesc, that.prodDesc) &&
Objects.equals(prodPrice, that.prodPrice);
}
@Override
public int hashCode() {
return Objects.hash(prodName, categoryId, prodDesc, prodPrice);
}
@Override
public String toString() {
return "ProductRequest{" +
"prodName='" + prodName + '\'' +
", categoryId=" + categoryId +
", prodDesc='" + prodDesc + '\'' +
", prodPrice=" + prodPrice +
'}';
}
}

View File

@@ -0,0 +1,121 @@
package com.petshop.backend.dto.product;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
public class ProductResponse {
private Long prodId;
private String prodName;
private Long categoryId;
private String categoryName;
private String prodDesc;
private BigDecimal prodPrice;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public ProductResponse() {
}
public ProductResponse(Long prodId, String prodName, Long categoryId, String categoryName, String prodDesc, BigDecimal prodPrice, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.prodId = prodId;
this.prodName = prodName;
this.categoryId = categoryId;
this.categoryName = categoryName;
this.prodDesc = prodDesc;
this.prodPrice = prodPrice;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getProdDesc() {
return prodDesc;
}
public void setProdDesc(String prodDesc) {
this.prodDesc = prodDesc;
}
public BigDecimal getProdPrice() {
return prodPrice;
}
public void setProdPrice(BigDecimal prodPrice) {
this.prodPrice = prodPrice;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductResponse that = (ProductResponse) o;
return Objects.equals(prodId, that.prodId) && Objects.equals(prodName, that.prodName) && Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(prodDesc, that.prodDesc) && Objects.equals(prodPrice, that.prodPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(prodId, prodName, categoryId, categoryName, prodDesc, prodPrice, createdAt, updatedAt);
}
@Override
public String toString() {
return "ProductResponse{" +
"prodId=" + prodId +
", prodName='" + prodName + '\'' +
", categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", prodDesc='" + prodDesc + '\'' +
", prodPrice=" + prodPrice +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,38 @@
package com.petshop.backend.dto.productsupplier;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Objects;
public class BulkDeleteProductSupplierRequest {
@NotEmpty(message = "Keys list cannot be empty")
private List<ProductSupplierKey> keys;
public List<ProductSupplierKey> getKeys() {
return keys;
}
public void setKeys(List<ProductSupplierKey> keys) {
this.keys = keys;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BulkDeleteProductSupplierRequest that = (BulkDeleteProductSupplierRequest) o;
return Objects.equals(keys, that.keys);
}
@Override
public int hashCode() {
return Objects.hash(keys);
}
@Override
public String toString() {
return "BulkDeleteProductSupplierRequest{" +
"keys=" + keys +
'}';
}
}

View File

@@ -0,0 +1,54 @@
package com.petshop.backend.dto.productsupplier;
import java.util.Objects;
public class ProductSupplierKey {
private Long productId;
private Long supplierId;
public ProductSupplierKey() {
}
public ProductSupplierKey(Long productId, Long supplierId) {
this.productId = productId;
this.supplierId = supplierId;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductSupplierKey that = (ProductSupplierKey) o;
return Objects.equals(productId, that.productId) && Objects.equals(supplierId, that.supplierId);
}
@Override
public int hashCode() {
return Objects.hash(productId, supplierId);
}
@Override
public String toString() {
return "ProductSupplierKey{" +
"productId=" + productId +
", supplierId=" + supplierId +
'}';
}
}

View File

@@ -0,0 +1,66 @@
package com.petshop.backend.dto.productsupplier;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.util.Objects;
public class ProductSupplierRequest {
@NotNull(message = "Product ID is required")
private Long productId;
@NotNull(message = "Supplier ID is required")
private Long supplierId;
@NotNull(message = "Cost is required")
@Positive(message = "Cost must be positive")
private BigDecimal cost;
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductSupplierRequest that = (ProductSupplierRequest) o;
return Objects.equals(productId, that.productId) &&
Objects.equals(supplierId, that.supplierId) &&
Objects.equals(cost, that.cost);
}
@Override
public int hashCode() {
return Objects.hash(productId, supplierId, cost);
}
@Override
public String toString() {
return "ProductSupplierRequest{" +
"productId=" + productId +
", supplierId=" + supplierId +
", cost=" + cost +
'}';
}
}

View File

@@ -0,0 +1,110 @@
package com.petshop.backend.dto.productsupplier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
public class ProductSupplierResponse {
private Long productId;
private String productName;
private Long supplierId;
private String supplierName;
private BigDecimal cost;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public ProductSupplierResponse() {
}
public ProductSupplierResponse(Long productId, String productName, Long supplierId, String supplierName, BigDecimal cost, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.productId = productId;
this.productName = productName;
this.supplierId = supplierId;
this.supplierName = supplierName;
this.cost = cost;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public String getSupplierName() {
return supplierName;
}
public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductSupplierResponse that = (ProductSupplierResponse) o;
return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(cost, that.cost) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(productId, productName, supplierId, supplierName, cost, createdAt, updatedAt);
}
@Override
public String toString() {
return "ProductSupplierResponse{" +
"productId=" + productId +
", productName='" + productName + '\'' +
", supplierId=" + supplierId +
", supplierName='" + supplierName + '\'' +
", cost=" + cost +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,110 @@
package com.petshop.backend.dto.purchaseorder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
public class PurchaseOrderResponse {
private Long purchaseOrderId;
private Long supId;
private String supplierName;
private LocalDate orderDate;
private String status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public PurchaseOrderResponse() {
}
public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.purchaseOrderId = purchaseOrderId;
this.supId = supId;
this.supplierName = supplierName;
this.orderDate = orderDate;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getPurchaseOrderId() {
return purchaseOrderId;
}
public void setPurchaseOrderId(Long purchaseOrderId) {
this.purchaseOrderId = purchaseOrderId;
}
public Long getSupId() {
return supId;
}
public void setSupId(Long supId) {
this.supId = supId;
}
public String getSupplierName() {
return supplierName;
}
public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}
public LocalDate getOrderDate() {
return orderDate;
}
public void setOrderDate(LocalDate orderDate) {
this.orderDate = orderDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PurchaseOrderResponse that = (PurchaseOrderResponse) o;
return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(status, that.status) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(purchaseOrderId, supId, supplierName, orderDate, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "PurchaseOrderResponse{" +
"purchaseOrderId=" + purchaseOrderId +
", supId=" + supId +
", supplierName='" + supplierName + '\'' +
", orderDate=" + orderDate +
", status='" + status + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,51 @@
package com.petshop.backend.dto.refund;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Objects;
public class RefundRequest {
@NotNull(message = "Sale ID is required")
private Long saleId;
@NotBlank(message = "Reason is required")
private String reason;
public Long getSaleId() {
return saleId;
}
public void setSaleId(Long saleId) {
this.saleId = saleId;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RefundRequest that = (RefundRequest) o;
return Objects.equals(saleId, that.saleId) &&
Objects.equals(reason, that.reason);
}
@Override
public int hashCode() {
return Objects.hash(saleId, reason);
}
@Override
public String toString() {
return "RefundRequest{" +
"saleId=" + saleId +
", reason='" + reason + '\'' +
'}';
}
}

View File

@@ -0,0 +1,128 @@
package com.petshop.backend.dto.refund;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
public class RefundResponse {
private Long id;
private Long saleId;
private Long customerId;
private BigDecimal amount;
private String reason;
private String status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public RefundResponse() {
}
public RefundResponse(Long id, Long saleId, Long customerId, BigDecimal amount, String reason, String status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.saleId = saleId;
this.customerId = customerId;
this.amount = amount;
this.reason = reason;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getSaleId() {
return saleId;
}
public void setSaleId(Long saleId) {
this.saleId = saleId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RefundResponse that = (RefundResponse) o;
return Objects.equals(id, that.id) &&
Objects.equals(saleId, that.saleId) &&
Objects.equals(customerId, that.customerId) &&
Objects.equals(amount, that.amount) &&
Objects.equals(reason, that.reason) &&
Objects.equals(status, that.status) &&
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, saleId, customerId, amount, reason, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "RefundResponse{" +
"id=" + id +
", saleId=" + saleId +
", customerId=" + customerId +
", amount=" + amount +
", reason='" + reason + '\'' +
", status='" + status + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,37 @@
package com.petshop.backend.dto.refund;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class RefundUpdateRequest {
@NotBlank(message = "Status is required")
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RefundUpdateRequest that = (RefundUpdateRequest) o;
return Objects.equals(status, that.status);
}
@Override
public int hashCode() {
return Objects.hash(status);
}
@Override
public String toString() {
return "RefundUpdateRequest{" +
"status='" + status + '\'' +
'}';
}
}

View File

@@ -0,0 +1,51 @@
package com.petshop.backend.dto.sale;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.Objects;
public class SaleItemRequest {
@NotNull(message = "Product ID is required")
private Long prodId;
@NotNull(message = "Quantity is required")
private Integer quantity;
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SaleItemRequest that = (SaleItemRequest) o;
return Objects.equals(prodId, that.prodId) &&
Objects.equals(quantity, that.quantity);
}
@Override
public int hashCode() {
return Objects.hash(prodId, quantity);
}
@Override
public String toString() {
return "SaleItemRequest{" +
"prodId=" + prodId +
", quantity=" + quantity +
'}';
}
}

View File

@@ -0,0 +1,102 @@
package com.petshop.backend.dto.sale;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
public class SaleRequest {
@NotNull(message = "Store ID is required")
private Long storeId;
private String paymentMethod;
@NotEmpty(message = "At least one item is required")
@Valid
private List<SaleItemRequest> items;
private Boolean isRefund = false;
private Long originalSaleId;
private Long customerId;
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public List<SaleItemRequest> getItems() {
return items;
}
public void setItems(List<SaleItemRequest> items) {
this.items = items;
}
public Boolean getIsRefund() {
return isRefund;
}
public void setIsRefund(Boolean isRefund) {
this.isRefund = isRefund;
}
public Long getOriginalSaleId() {
return originalSaleId;
}
public void setOriginalSaleId(Long originalSaleId) {
this.originalSaleId = originalSaleId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SaleRequest that = (SaleRequest) o;
return Objects.equals(storeId, that.storeId) &&
Objects.equals(paymentMethod, that.paymentMethod) &&
Objects.equals(items, that.items) &&
Objects.equals(isRefund, that.isRefund) &&
Objects.equals(originalSaleId, that.originalSaleId) &&
Objects.equals(customerId, that.customerId);
}
@Override
public int hashCode() {
return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId);
}
@Override
public String toString() {
return "SaleRequest{" +
"storeId=" + storeId +
", paymentMethod='" + paymentMethod + '\'' +
", items=" + items +
", isRefund=" + isRefund +
", originalSaleId=" + originalSaleId +
", customerId=" + customerId +
'}';
}
}

View File

@@ -0,0 +1,249 @@
package com.petshop.backend.dto.sale;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
public class SaleResponse {
private Long saleId;
private LocalDateTime saleDate;
private Long employeeId;
private String employeeName;
private Long storeId;
private String storeName;
private BigDecimal totalAmount;
private String paymentMethod;
private Boolean isRefund;
private Long originalSaleId;
private List<SaleItemResponse> items;
private LocalDateTime createdAt;
public SaleResponse() {
}
public SaleResponse(Long saleId, LocalDateTime saleDate, Long employeeId, String employeeName, Long storeId, String storeName, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Long originalSaleId, List<SaleItemResponse> items, LocalDateTime createdAt) {
this.saleId = saleId;
this.saleDate = saleDate;
this.employeeId = employeeId;
this.employeeName = employeeName;
this.storeId = storeId;
this.storeName = storeName;
this.totalAmount = totalAmount;
this.paymentMethod = paymentMethod;
this.isRefund = isRefund;
this.originalSaleId = originalSaleId;
this.items = items;
this.createdAt = createdAt;
}
public Long getSaleId() {
return saleId;
}
public void setSaleId(Long saleId) {
this.saleId = saleId;
}
public LocalDateTime getSaleDate() {
return saleDate;
}
public void setSaleDate(LocalDateTime saleDate) {
this.saleDate = saleDate;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public Boolean getIsRefund() {
return isRefund;
}
public void setIsRefund(Boolean isRefund) {
this.isRefund = isRefund;
}
public Long getOriginalSaleId() {
return originalSaleId;
}
public void setOriginalSaleId(Long originalSaleId) {
this.originalSaleId = originalSaleId;
}
public List<SaleItemResponse> getItems() {
return items;
}
public void setItems(List<SaleItemResponse> items) {
this.items = items;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SaleResponse that = (SaleResponse) o;
return Objects.equals(saleId, that.saleId) && Objects.equals(saleDate, that.saleDate) && Objects.equals(employeeId, that.employeeId) && Objects.equals(employeeName, that.employeeName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(totalAmount, that.totalAmount) && Objects.equals(paymentMethod, that.paymentMethod) && Objects.equals(isRefund, that.isRefund) && Objects.equals(originalSaleId, that.originalSaleId) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt);
}
@Override
public int hashCode() {
return Objects.hash(saleId, saleDate, employeeId, employeeName, storeId, storeName, totalAmount, paymentMethod, isRefund, originalSaleId, items, createdAt);
}
@Override
public String toString() {
return "SaleResponse{" +
"saleId=" + saleId +
", saleDate=" + saleDate +
", employeeId=" + employeeId +
", employeeName='" + employeeName + '\'' +
", storeId=" + storeId +
", storeName='" + storeName + '\'' +
", totalAmount=" + totalAmount +
", paymentMethod='" + paymentMethod + '\'' +
", isRefund=" + isRefund +
", originalSaleId=" + originalSaleId +
", items=" + items +
", createdAt=" + createdAt +
'}';
}
public static class SaleItemResponse {
private Long saleItemId;
private Long prodId;
private String productName;
private Integer quantity;
private BigDecimal unitPrice;
public SaleItemResponse() {
}
public SaleItemResponse(Long saleItemId, Long prodId, String productName, Integer quantity, BigDecimal unitPrice) {
this.saleItemId = saleItemId;
this.prodId = prodId;
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public Long getSaleItemId() {
return saleItemId;
}
public void setSaleItemId(Long saleItemId) {
this.saleItemId = saleItemId;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public BigDecimal getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(BigDecimal unitPrice) {
this.unitPrice = unitPrice;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SaleItemResponse that = (SaleItemResponse) o;
return Objects.equals(saleItemId, that.saleItemId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitPrice, that.unitPrice);
}
@Override
public int hashCode() {
return Objects.hash(saleItemId, prodId, productName, quantity, unitPrice);
}
@Override
public String toString() {
return "SaleItemResponse{" +
"saleItemId=" + saleItemId +
", prodId=" + prodId +
", productName='" + productName + '\'' +
", quantity=" + quantity +
", unitPrice=" + unitPrice +
'}';
}
}
}

View File

@@ -0,0 +1,79 @@
package com.petshop.backend.dto.service;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.util.Objects;
public class ServiceRequest {
@NotBlank(message = "Service name is required")
private String serviceName;
private String serviceDesc;
@NotNull(message = "Service price is required")
@Positive(message = "Price must be positive")
private BigDecimal servicePrice;
@Positive(message = "Duration must be positive")
private Integer serviceDuration;
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceDesc() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc = serviceDesc;
}
public BigDecimal getServicePrice() {
return servicePrice;
}
public void setServicePrice(BigDecimal servicePrice) {
this.servicePrice = servicePrice;
}
public Integer getServiceDuration() {
return serviceDuration;
}
public void setServiceDuration(Integer serviceDuration) {
this.serviceDuration = serviceDuration;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceRequest that = (ServiceRequest) o;
return Objects.equals(serviceName, that.serviceName) &&
Objects.equals(serviceDesc, that.serviceDesc) &&
Objects.equals(servicePrice, that.servicePrice) &&
Objects.equals(serviceDuration, that.serviceDuration);
}
@Override
public int hashCode() {
return Objects.hash(serviceName, serviceDesc, servicePrice, serviceDuration);
}
@Override
public String toString() {
return "ServiceRequest{" +
"serviceName='" + serviceName + '\'' +
", serviceDesc='" + serviceDesc + '\'' +
", servicePrice=" + servicePrice +
", serviceDuration=" + serviceDuration +
'}';
}
}

View File

@@ -0,0 +1,110 @@
package com.petshop.backend.dto.service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
public class ServiceResponse {
private Long serviceId;
private String serviceName;
private String serviceDesc;
private BigDecimal servicePrice;
private Integer serviceDuration;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public ServiceResponse() {
}
public ServiceResponse(Long serviceId, String serviceName, String serviceDesc, BigDecimal servicePrice, Integer serviceDuration, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.serviceId = serviceId;
this.serviceName = serviceName;
this.serviceDesc = serviceDesc;
this.servicePrice = servicePrice;
this.serviceDuration = serviceDuration;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceDesc() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc = serviceDesc;
}
public BigDecimal getServicePrice() {
return servicePrice;
}
public void setServicePrice(BigDecimal servicePrice) {
this.servicePrice = servicePrice;
}
public Integer getServiceDuration() {
return serviceDuration;
}
public void setServiceDuration(Integer serviceDuration) {
this.serviceDuration = serviceDuration;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceResponse that = (ServiceResponse) o;
return Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDuration, that.serviceDuration) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(serviceId, serviceName, serviceDesc, servicePrice, serviceDuration, createdAt, updatedAt);
}
@Override
public String toString() {
return "ServiceResponse{" +
"serviceId=" + serviceId +
", serviceName='" + serviceName + '\'' +
", serviceDesc='" + serviceDesc + '\'' +
", servicePrice=" + servicePrice +
", serviceDuration=" + serviceDuration +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,78 @@
package com.petshop.backend.dto.store;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class StoreRequest {
@NotBlank(message = "Store name is required")
private String storeName;
@NotBlank(message = "Address is required")
private String address;
@NotBlank(message = "Phone is required")
private String phone;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoreRequest that = (StoreRequest) o;
return Objects.equals(storeName, that.storeName) &&
Objects.equals(address, that.address) &&
Objects.equals(phone, that.phone) &&
Objects.equals(email, that.email);
}
@Override
public int hashCode() {
return Objects.hash(storeName, address, phone, email);
}
@Override
public String toString() {
return "StoreRequest{" +
"storeName='" + storeName + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
'}';
}
}

View File

@@ -0,0 +1,98 @@
package com.petshop.backend.dto.store;
import java.time.LocalDateTime;
import java.util.Objects;
public class StoreResponse {
private Long storeId;
private String storeName;
private String address;
private String phone;
private String email;
private LocalDateTime createdAt;
public StoreResponse() {
}
public StoreResponse(Long storeId, String storeName, String address, String phone, String email, LocalDateTime createdAt) {
this.storeId = storeId;
this.storeName = storeName;
this.address = address;
this.phone = phone;
this.email = email;
this.createdAt = createdAt;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoreResponse that = (StoreResponse) o;
return Objects.equals(storeId, that.storeId);
}
@Override
public int hashCode() {
return Objects.hash(storeId);
}
@Override
public String toString() {
return "StoreResponse{" +
"storeId=" + storeId +
", storeName='" + storeName + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", createdAt=" + createdAt +
'}';
}
}

View File

@@ -0,0 +1,87 @@
package com.petshop.backend.dto.supplier;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
public class SupplierRequest {
@NotBlank(message = "Supplier company is required")
private String supCompany;
private String supContactFirstName;
private String supContactLastName;
@Email(message = "Invalid email format")
private String supEmail;
private String supPhone;
public String getSupCompany() {
return supCompany;
}
public void setSupCompany(String supCompany) {
this.supCompany = supCompany;
}
public String getSupContactFirstName() {
return supContactFirstName;
}
public void setSupContactFirstName(String supContactFirstName) {
this.supContactFirstName = supContactFirstName;
}
public String getSupContactLastName() {
return supContactLastName;
}
public void setSupContactLastName(String supContactLastName) {
this.supContactLastName = supContactLastName;
}
public String getSupEmail() {
return supEmail;
}
public void setSupEmail(String supEmail) {
this.supEmail = supEmail;
}
public String getSupPhone() {
return supPhone;
}
public void setSupPhone(String supPhone) {
this.supPhone = supPhone;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SupplierRequest that = (SupplierRequest) o;
return Objects.equals(supCompany, that.supCompany) &&
Objects.equals(supContactFirstName, that.supContactFirstName) &&
Objects.equals(supContactLastName, that.supContactLastName) &&
Objects.equals(supEmail, that.supEmail) &&
Objects.equals(supPhone, that.supPhone);
}
@Override
public int hashCode() {
return Objects.hash(supCompany, supContactFirstName, supContactLastName, supEmail, supPhone);
}
@Override
public String toString() {
return "SupplierRequest{" +
"supCompany='" + supCompany + '\'' +
", supContactFirstName='" + supContactFirstName + '\'' +
", supContactLastName='" + supContactLastName + '\'' +
", supEmail='" + supEmail + '\'' +
", supPhone='" + supPhone + '\'' +
'}';
}
}

View File

@@ -0,0 +1,120 @@
package com.petshop.backend.dto.supplier;
import java.time.LocalDateTime;
import java.util.Objects;
public class SupplierResponse {
private Long supId;
private String supCompany;
private String supContactFirstName;
private String supContactLastName;
private String supEmail;
private String supPhone;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public SupplierResponse() {
}
public SupplierResponse(Long supId, String supCompany, String supContactFirstName, String supContactLastName, String supEmail, String supPhone, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.supId = supId;
this.supCompany = supCompany;
this.supContactFirstName = supContactFirstName;
this.supContactLastName = supContactLastName;
this.supEmail = supEmail;
this.supPhone = supPhone;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getSupId() {
return supId;
}
public void setSupId(Long supId) {
this.supId = supId;
}
public String getSupCompany() {
return supCompany;
}
public void setSupCompany(String supCompany) {
this.supCompany = supCompany;
}
public String getSupContactFirstName() {
return supContactFirstName;
}
public void setSupContactFirstName(String supContactFirstName) {
this.supContactFirstName = supContactFirstName;
}
public String getSupContactLastName() {
return supContactLastName;
}
public void setSupContactLastName(String supContactLastName) {
this.supContactLastName = supContactLastName;
}
public String getSupEmail() {
return supEmail;
}
public void setSupEmail(String supEmail) {
this.supEmail = supEmail;
}
public String getSupPhone() {
return supPhone;
}
public void setSupPhone(String supPhone) {
this.supPhone = supPhone;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SupplierResponse that = (SupplierResponse) o;
return Objects.equals(supId, that.supId) && Objects.equals(supCompany, that.supCompany) && Objects.equals(supContactFirstName, that.supContactFirstName) && Objects.equals(supContactLastName, that.supContactLastName) && Objects.equals(supEmail, that.supEmail) && Objects.equals(supPhone, that.supPhone) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone, createdAt, updatedAt);
}
@Override
public String toString() {
return "SupplierResponse{" +
"supId=" + supId +
", supCompany='" + supCompany + '\'' +
", supContactFirstName='" + supContactFirstName + '\'' +
", supContactLastName='" + supContactLastName + '\'' +
", supEmail='" + supEmail + '\'' +
", supPhone='" + supPhone + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,119 @@
package com.petshop.backend.dto.user;
import com.petshop.backend.entity.User;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Objects;
public class UserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
@NotBlank(message = "Full name is required")
private String fullName;
@Email(message = "Invalid email format")
private String email;
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
@NotNull(message = "Role is required")
private User.Role role;
private Boolean active = true;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public User.Role getRole() {
return role;
}
public void setRole(User.Role role) {
this.role = role;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRequest that = (UserRequest) o;
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(email, that.email) &&
Objects.equals(phone, that.phone) &&
role == that.role &&
Objects.equals(active, that.active);
}
@Override
public int hashCode() {
return Objects.hash(username, password, fullName, email, phone, role, active);
}
@Override
public String toString() {
return "UserRequest{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role=" + role +
", active=" + active +
'}';
}
}

View File

@@ -0,0 +1,131 @@
package com.petshop.backend.dto.user;
import java.time.LocalDateTime;
import java.util.Objects;
public class UserResponse {
private Long id;
private String username;
private String fullName;
private String email;
private String phone;
private String role;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public UserResponse() {
}
public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.fullName = fullName;
this.email = email;
this.phone = phone;
this.role = role;
this.active = active;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserResponse that = (UserResponse) o;
return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt);
}
@Override
public String toString() {
return "UserResponse{" +
"id=" + id +
", username='" + username + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role='" + role + '\'' +
", active=" + active +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,90 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "activityLog")
public class ActivityLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long logId;
@ManyToOne
@JoinColumn(name = "employeeId", nullable = false)
private Employee employee;
@Column(nullable = false, columnDefinition = "TEXT")
private String activity;
@Column(nullable = false)
private LocalDateTime logTimestamp = LocalDateTime.now();
public ActivityLog() {
}
public ActivityLog(Long logId, Employee employee, String activity, LocalDateTime logTimestamp) {
this.logId = logId;
this.employee = employee;
this.activity = activity;
this.logTimestamp = logTimestamp;
}
public Long getLogId() {
return logId;
}
public void setLogId(Long logId) {
this.logId = logId;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String getActivity() {
return activity;
}
public void setActivity(String activity) {
this.activity = activity;
}
public LocalDateTime getLogTimestamp() {
return logTimestamp;
}
public void setLogTimestamp(LocalDateTime logTimestamp) {
this.logTimestamp = logTimestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActivityLog that = (ActivityLog) o;
return Objects.equals(logId, that.logId);
}
@Override
public int hashCode() {
return Objects.hash(logId);
}
@Override
public String toString() {
return "ActivityLog{" +
"logId=" + logId +
", employee=" + employee +
", activity='" + activity + '\'' +
", logTimestamp=" + logTimestamp +
'}';
}
}

View File

@@ -0,0 +1,136 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "adoption")
public class Adoption {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long adoptionId;
@ManyToOne
@JoinColumn(name = "petId", nullable = false)
private Pet pet;
@ManyToOne
@JoinColumn(name = "customerId", nullable = false)
private Customer customer;
@Column(nullable = false)
private LocalDate adoptionDate;
@Column(nullable = false, length = 20)
private String adoptionStatus;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Adoption() {
}
public Adoption(Long adoptionId, Pet pet, Customer customer, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.adoptionId = adoptionId;
this.pet = pet;
this.customer = customer;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getAdoptionId() {
return adoptionId;
}
public void setAdoptionId(Long adoptionId) {
this.adoptionId = adoptionId;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(LocalDate adoptionDate) {
this.adoptionDate = adoptionDate;
}
public String getAdoptionStatus() {
return adoptionStatus;
}
public void setAdoptionStatus(String adoptionStatus) {
this.adoptionStatus = adoptionStatus;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Adoption adoption = (Adoption) o;
return Objects.equals(adoptionId, adoption.adoptionId);
}
@Override
public int hashCode() {
return Objects.hash(adoptionId);
}
@Override
public String toString() {
return "Adoption{" +
"adoptionId=" + adoptionId +
", pet=" + pet +
", customer=" + customer +
", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,183 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@Entity
@Table(name = "appointment")
public class Appointment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long appointmentId;
@ManyToOne
@JoinColumn(name = "customerId", nullable = false)
private Customer customer;
@ManyToOne
@JoinColumn(name = "storeId", nullable = false)
private StoreLocation store;
@ManyToOne
@JoinColumn(name = "serviceId", nullable = false)
private Service service;
@Column(nullable = false)
private LocalDate appointmentDate;
@Column(nullable = false)
private LocalTime appointmentTime;
@Column(nullable = false, length = 20)
private String appointmentStatus;
@ManyToMany
@JoinTable(
name = "appointmentPet",
joinColumns = @JoinColumn(name = "appointmentId"),
inverseJoinColumns = @JoinColumn(name = "petId")
)
private Set<Pet> pets = new HashSet<>();
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Appointment() {
}
public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set<Pet> pets, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.appointmentId = appointmentId;
this.customer = customer;
this.store = store;
this.service = service;
this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime;
this.appointmentStatus = appointmentStatus;
this.pets = pets;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getAppointmentId() {
return appointmentId;
}
public void setAppointmentId(Long appointmentId) {
this.appointmentId = appointmentId;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public StoreLocation getStore() {
return store;
}
public void setStore(StoreLocation store) {
this.store = store;
}
public Service getService() {
return service;
}
public void setService(Service service) {
this.service = service;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public String getAppointmentStatus() {
return appointmentStatus;
}
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
public Set<Pet> getPets() {
return pets;
}
public void setPets(Set<Pet> pets) {
this.pets = pets;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Appointment that = (Appointment) o;
return Objects.equals(appointmentId, that.appointmentId);
}
@Override
public int hashCode() {
return Objects.hash(appointmentId);
}
@Override
public String toString() {
return "Appointment{" +
"appointmentId=" + appointmentId +
", customer=" + customer +
", store=" + store +
", service=" + service +
", appointmentDate=" + appointmentDate +
", appointmentTime=" + appointmentTime +
", appointmentStatus='" + appointmentStatus + '\'' +
", pets=" + pets +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,106 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long categoryId;
@Column(nullable = false, length = 100)
private String categoryName;
@Column(nullable = false, length = 50)
private String categoryType;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Category() {
}
public Category(Long categoryId, String categoryName, String categoryType, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.categoryId = categoryId;
this.categoryName = categoryName;
this.categoryType = categoryType;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryType() {
return categoryType;
}
public void setCategoryType(String categoryType) {
this.categoryType = categoryType;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Category category = (Category) o;
return Objects.equals(categoryId, category.categoryId);
}
@Override
public int hashCode() {
return Objects.hash(categoryId);
}
@Override
public String toString() {
return "Category{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", categoryType='" + categoryType + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,127 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Entity
@Table(name = "conversation")
public class Conversation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column
private Long staffId;
@Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)")
private ConversationStatus status = ConversationStatus.OPEN;
@Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)")
private ConversationMode mode = ConversationMode.AUTOMATED;
@Column
private LocalDateTime humanRequestedAt;
@CreationTimestamp
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
public enum ConversationStatus {
OPEN, CLOSED
}
public enum ConversationMode {
AUTOMATED, HUMAN
}
public Conversation() {
}
public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, ConversationMode mode, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.customerId = customerId;
this.staffId = staffId;
this.status = status;
this.mode = mode;
this.humanRequestedAt = humanRequestedAt;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getStaffId() {
return staffId;
}
public void setStaffId(Long staffId) {
this.staffId = staffId;
}
public ConversationStatus getStatus() {
return status;
}
public void setStatus(ConversationStatus status) {
this.status = status;
}
public ConversationMode getMode() {
return mode;
}
public void setMode(ConversationMode mode) {
this.mode = mode;
}
public LocalDateTime getHumanRequestedAt() {
return humanRequestedAt;
}
public void setHumanRequestedAt(LocalDateTime humanRequestedAt) {
this.humanRequestedAt = humanRequestedAt;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,132 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long customerId;
@Column(name = "user_id")
private Long userId;
@Column(nullable = false, length = 50)
private String firstName;
@Column(nullable = false, length = 50)
private String lastName;
@Column(nullable = false, length = 100)
private String email;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Customer() {
}
public Customer(Long customerId, Long userId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.customerId = customerId;
this.userId = userId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Customer customer = (Customer) o;
return Objects.equals(customerId, customer.customerId);
}
@Override
public int hashCode() {
return Objects.hash(customerId);
}
@Override
public String toString() {
return "Customer{" +
"customerId=" + customerId +
", userId=" + userId +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,158 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long employeeId;
@Column(name = "user_id")
private Long userId;
@Column(nullable = false, length = 50)
private String firstName;
@Column(nullable = false, length = 50)
private String lastName;
@Column(nullable = false, length = 100)
private String email;
@Column(nullable = false, length = 50)
private String role;
@Column(nullable = false)
private Boolean isActive = true;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Employee() {
}
public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.employeeId = employeeId;
this.userId = userId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.role = role;
this.isActive = isActive;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(employeeId, employee.employeeId);
}
@Override
public int hashCode() {
return Objects.hash(employeeId);
}
@Override
public String toString() {
return "Employee{" +
"employeeId=" + employeeId +
", userId=" + userId +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", role='" + role + '\'' +
", isActive=" + isActive +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,117 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "employeeStore")
@IdClass(EmployeeStore.EmployeeStoreId.class)
public class EmployeeStore {
@Id
@ManyToOne
@JoinColumn(name = "employeeId", nullable = false)
private Employee employee;
@Id
@ManyToOne
@JoinColumn(name = "storeId", nullable = false)
private StoreLocation store;
public EmployeeStore() {
}
public EmployeeStore(Employee employee, StoreLocation store) {
this.employee = employee;
this.store = store;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public StoreLocation getStore() {
return store;
}
public void setStore(StoreLocation store) {
this.store = store;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeStore that = (EmployeeStore) o;
return Objects.equals(employee, that.employee) && Objects.equals(store, that.store);
}
@Override
public int hashCode() {
return Objects.hash(employee, store);
}
@Override
public String toString() {
return "EmployeeStore{" +
"employee=" + employee +
", store=" + store +
'}';
}
public static class EmployeeStoreId implements Serializable {
private Long employee;
private Long store;
public EmployeeStoreId() {
}
public EmployeeStoreId(Long employee, Long store) {
this.employee = employee;
this.store = store;
}
public Long getEmployee() {
return employee;
}
public void setEmployee(Long employee) {
this.employee = employee;
}
public Long getStore() {
return store;
}
public void setStore(Long store) {
this.store = store;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeStoreId that = (EmployeeStoreId) o;
return Objects.equals(employee, that.employee) && Objects.equals(store, that.store);
}
@Override
public int hashCode() {
return Objects.hash(employee, store);
}
@Override
public String toString() {
return "EmployeeStoreId{" +
"employee=" + employee +
", store=" + store +
'}';
}
}
}

View File

@@ -0,0 +1,107 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "inventory")
public class Inventory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long inventoryId;
@ManyToOne
@JoinColumn(name = "prodId", nullable = false)
private Product product;
@Column(nullable = false)
private Integer quantity = 0;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Inventory() {
}
public Inventory(Long inventoryId, Product product, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.inventoryId = inventoryId;
this.product = product;
this.quantity = quantity;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getInventoryId() {
return inventoryId;
}
public void setInventoryId(Long inventoryId) {
this.inventoryId = inventoryId;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Inventory inventory = (Inventory) o;
return Objects.equals(inventoryId, inventory.inventoryId);
}
@Override
public int hashCode() {
return Objects.hash(inventoryId);
}
@Override
public String toString() {
return "Inventory{" +
"inventoryId=" + inventoryId +
", product=" + product +
", quantity=" + quantity +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -0,0 +1,91 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Entity
@Table(name = "message")
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long conversationId;
@Column(nullable = false)
private Long senderId;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime timestamp;
@Column(nullable = false)
private Boolean isRead = false;
public Message() {
}
public Message(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) {
this.id = id;
this.conversationId = conversationId;
this.senderId = senderId;
this.content = content;
this.timestamp = timestamp;
this.isRead = isRead;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getConversationId() {
return conversationId;
}
public void setConversationId(Long conversationId) {
this.conversationId = conversationId;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public Boolean getIsRead() {
return isRead;
}
public void setIsRead(Boolean isRead) {
this.isRead = isRead;
}
}

Some files were not shown because too many files have changed in this diff Show More