Compare commits
11 Commits
73d0d767e9
...
v0.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1479827612 | ||
|
|
b3f09d5dfc | ||
|
|
4c83f6670d | ||
|
|
d9e69e03ad | ||
|
|
c1c326aa5d | ||
|
|
c26ee4f400 | ||
|
|
0f7339d5c1 | ||
|
|
386f5137c6 | ||
|
|
b5dcba1e11 | ||
|
|
854a0bace8 | ||
|
|
fda1f39901 |
@@ -147,3 +147,11 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
VITE_APP_VERSION=${{ steps.generate.outputs.new_tag }}
|
VITE_APP_VERSION=${{ steps.generate.outputs.new_tag }}
|
||||||
VITE_BASE_URL=https://todo.almazlar.com/api
|
VITE_BASE_URL=https://todo.almazlar.com/api
|
||||||
|
|
||||||
|
- name: Deploy to Dokploy (Backend)
|
||||||
|
run: |
|
||||||
|
curl -X POST "${{ secrets.DOKPLOY_BACKEND_WEBHOOK_URL }}"
|
||||||
|
|
||||||
|
- name: Deploy to Dokploy (Frontend)
|
||||||
|
run: |
|
||||||
|
curl -X POST "${{ secrets.DOKPLOY_FRONTEND_WEBHOOK_URL }}"
|
||||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,3 +1,20 @@
|
|||||||
|
## [v0.0.10] - 2026-02-22
|
||||||
|
|
||||||
|
* feat: add deployment steps for Backend and Frontend to Dokploy (b3f09d5)
|
||||||
|
* fix: update VITE_BASE_URL to use port 8080 in Dockerfile (4c83f66)
|
||||||
|
* fix: update server port to 8080 in Dockerfile and application properties (d9e69e0)
|
||||||
|
|
||||||
|
## [v0.0.9] - 2026-02-22
|
||||||
|
|
||||||
|
* feat: add createdAt field to Todo entity and update database migration script (c26ee4f)
|
||||||
|
|
||||||
|
## [v0.0.8] - 2026-02-22
|
||||||
|
|
||||||
|
* fix: update Dockerfile, application properties, and controller mappings for health check and CORS support (386f513)
|
||||||
|
* fix: update application properties and Dockerfile for local development configuration (b5dcba1)
|
||||||
|
* fix: update Dockerfile to run tests during build and adjust TodoController request mapping (854a0ba)
|
||||||
|
* fix: add CrossOrigin annotation to TodoController for CORS support (fda1f39)
|
||||||
|
|
||||||
## [v0.0.7] - 2026-02-22
|
## [v0.0.7] - 2026-02-22
|
||||||
|
|
||||||
* fix: update API base URL and add healthcheck for backend service (4050c08)
|
* fix: update API base URL and add healthcheck for backend service (4050c08)
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
# Build stage
|
# Build stage ---------------------------------------------------------------
|
||||||
FROM maven:3.9.12-eclipse-temurin-25 AS build
|
FROM maven:3.9.12-eclipse-temurin-25 AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN mvn clean package -DskipTests
|
RUN mvn clean package
|
||||||
|
|
||||||
# Run stage
|
# Run stage ---------------------------------------------------------------
|
||||||
FROM eclipse-temurin:25-jre
|
FROM eclipse-temurin:25-jre
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG APP_VERSION=dev
|
ARG APP_VERSION=dev
|
||||||
ENV APP_VERSION=${APP_VERSION}
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# Install curl (required for the health‑check)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y curl && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
COPY --from=build /app/target/*.jar app.jar
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
|
CMD curl -f http://localhost:8080/api/actuator/health || exit 1
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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"
|
<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">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
@@ -65,8 +66,23 @@
|
|||||||
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-flyway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-database-postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
registry.addMapping("/**")
|
registry.addMapping("/**")
|
||||||
.allowedOrigins("http://localhost:5173", "http://localhost:3000", "http://localhost:8081",
|
.allowedOrigins("http://localhost:5173", "https://todo.almazlar.com")
|
||||||
"http://localhost:80", "https://todo.almazlar.com") // Typical Vite/React/Docker ports
|
|
||||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
.allowedHeaders("*")
|
.allowedHeaders("*")
|
||||||
.allowCredentials(true);
|
.allowCredentials(true);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/todos")
|
@RequestMapping("/todos")
|
||||||
public class TodoController {
|
public class TodoController {
|
||||||
|
|
||||||
private final TodoService todoService;
|
private final TodoService todoService;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/version")
|
@RequestMapping("/version")
|
||||||
public class VersionController {
|
public class VersionController {
|
||||||
|
|
||||||
@Value("${APP_VERSION:dev}")
|
@Value("${APP_VERSION:dev}")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.todo.backend.model;
|
package com.todo.backend.model;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -19,6 +21,9 @@ public class Todo {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean completed = false;
|
private boolean completed = false;
|
||||||
|
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt = LocalDateTime.now();
|
||||||
|
|
||||||
// Default constructor is required by JPA
|
// Default constructor is required by JPA
|
||||||
public Todo() {
|
public Todo() {
|
||||||
}
|
}
|
||||||
@@ -62,4 +67,9 @@ public class Todo {
|
|||||||
public void setCompleted(boolean completed) {
|
public void setCompleted(boolean completed) {
|
||||||
this.completed = completed;
|
this.completed = completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
server.port=8080
|
||||||
|
server.servlet.context-path=/api
|
||||||
spring.application.name=backend
|
spring.application.name=backend
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/tododb
|
|
||||||
spring.datasource.username=postgres
|
# Flyway
|
||||||
spring.datasource.password=postgres
|
spring.flyway.enabled=true
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.flyway.locations=classpath:db/migration
|
||||||
|
spring.flyway.baseline-on-migrate=true
|
||||||
|
|
||||||
|
# Database
|
||||||
|
spring.jpa.hibernate.ddl-auto=none
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
management.endpoints.web.exposure.include=health,info,metrics
|
||||||
|
management.endpoint.health.show-details=always
|
||||||
7
backend/src/main/resources/db/migration/V1__init.sql
Normal file
7
backend/src/main/resources/db/migration/V1__init.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE todos (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
// Spring Boot 3.4+ replaces @MockBean with @MockitoBean
|
// Spring Boot 3.4+ replaces @MockBean with @MockitoBean
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -25,6 +26,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
|||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
|
|
||||||
@WebMvcTest(TodoController.class)
|
@WebMvcTest(TodoController.class)
|
||||||
|
@ActiveProfiles("test")
|
||||||
public class TodoControllerTest {
|
public class TodoControllerTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -52,7 +54,7 @@ public class TodoControllerTest {
|
|||||||
List<Todo> todos = Arrays.asList(todo1, todo2);
|
List<Todo> todos = Arrays.asList(todo1, todo2);
|
||||||
when(todoService.getAllTodos()).thenReturn(todos);
|
when(todoService.getAllTodos()).thenReturn(todos);
|
||||||
|
|
||||||
mockMvc.perform(get("/api/todos"))
|
mockMvc.perform(get("/todos"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$", hasSize(2)))
|
.andExpect(jsonPath("$", hasSize(2)))
|
||||||
.andExpect(jsonPath("$[0].title", is(todo1.getTitle())))
|
.andExpect(jsonPath("$[0].title", is(todo1.getTitle())))
|
||||||
@@ -63,7 +65,7 @@ public class TodoControllerTest {
|
|||||||
void getTodoById_WhenExists_ReturnsTodo() throws Exception {
|
void getTodoById_WhenExists_ReturnsTodo() throws Exception {
|
||||||
when(todoService.getTodoById(1L)).thenReturn(Optional.of(todo1));
|
when(todoService.getTodoById(1L)).thenReturn(Optional.of(todo1));
|
||||||
|
|
||||||
mockMvc.perform(get("/api/todos/1"))
|
mockMvc.perform(get("/todos/1"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.title", is(todo1.getTitle())))
|
.andExpect(jsonPath("$.title", is(todo1.getTitle())))
|
||||||
.andExpect(jsonPath("$.id", is(1)));
|
.andExpect(jsonPath("$.id", is(1)));
|
||||||
@@ -73,7 +75,7 @@ public class TodoControllerTest {
|
|||||||
void getTodoById_WhenNotExists_ReturnsNotFound() throws Exception {
|
void getTodoById_WhenNotExists_ReturnsNotFound() throws Exception {
|
||||||
when(todoService.getTodoById(99L)).thenReturn(Optional.empty());
|
when(todoService.getTodoById(99L)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
mockMvc.perform(get("/api/todos/99"))
|
mockMvc.perform(get("/todos/99"))
|
||||||
.andExpect(status().isNotFound());
|
.andExpect(status().isNotFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ public class TodoControllerTest {
|
|||||||
|
|
||||||
when(todoService.createTodo(any(Todo.class))).thenReturn(savedTodo);
|
when(todoService.createTodo(any(Todo.class))).thenReturn(savedTodo);
|
||||||
|
|
||||||
mockMvc.perform(post("/api/todos")
|
mockMvc.perform(post("/todos")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(newTodo)))
|
.content(objectMapper.writeValueAsString(newTodo)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
@@ -101,7 +103,7 @@ public class TodoControllerTest {
|
|||||||
|
|
||||||
when(todoService.updateTodo(eq(1L), any(Todo.class))).thenReturn(updatedTodo);
|
when(todoService.updateTodo(eq(1L), any(Todo.class))).thenReturn(updatedTodo);
|
||||||
|
|
||||||
mockMvc.perform(put("/api/todos/1")
|
mockMvc.perform(put("/todos/1")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(updatedInfo)))
|
.content(objectMapper.writeValueAsString(updatedInfo)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
@@ -115,7 +117,7 @@ public class TodoControllerTest {
|
|||||||
|
|
||||||
when(todoService.updateTodo(eq(99L), any(Todo.class))).thenReturn(null);
|
when(todoService.updateTodo(eq(99L), any(Todo.class))).thenReturn(null);
|
||||||
|
|
||||||
mockMvc.perform(put("/api/todos/99")
|
mockMvc.perform(put("/todos/99")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(objectMapper.writeValueAsString(updatedInfo)))
|
.content(objectMapper.writeValueAsString(updatedInfo)))
|
||||||
.andExpect(status().isNotFound());
|
.andExpect(status().isNotFound());
|
||||||
@@ -125,7 +127,7 @@ public class TodoControllerTest {
|
|||||||
void deleteTodo_WhenExists_ReturnsNoContent() throws Exception {
|
void deleteTodo_WhenExists_ReturnsNoContent() throws Exception {
|
||||||
when(todoService.deleteTodo(1L)).thenReturn(true);
|
when(todoService.deleteTodo(1L)).thenReturn(true);
|
||||||
|
|
||||||
mockMvc.perform(delete("/api/todos/1"))
|
mockMvc.perform(delete("/todos/1"))
|
||||||
.andExpect(status().isNoContent());
|
.andExpect(status().isNoContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +135,9 @@ public class TodoControllerTest {
|
|||||||
void deleteTodo_WhenNotExists_ReturnsNotFound() throws Exception {
|
void deleteTodo_WhenNotExists_ReturnsNotFound() throws Exception {
|
||||||
when(todoService.deleteTodo(99L)).thenReturn(false);
|
when(todoService.deleteTodo(99L)).thenReturn(false);
|
||||||
|
|
||||||
mockMvc.perform(delete("/api/todos/99"))
|
mockMvc.perform(delete("/todos/99"))
|
||||||
.andExpect(status().isNotFound());
|
.andExpect(status().isNotFound());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ spring.datasource.username=sa
|
|||||||
spring.datasource.password=
|
spring.datasource.password=
|
||||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||||
spring.jpa.hibernate.ddl-auto=create-drop
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
|
||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
build: ./backend
|
build: ./backend
|
||||||
container_name: todo-backend
|
container_name: todo-backend
|
||||||
ports:
|
ports:
|
||||||
- "8082:8080"
|
- "8082:8082"
|
||||||
environment:
|
environment:
|
||||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tododb
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tododb
|
||||||
- SPRING_DATASOURCE_USERNAME=postgres
|
- SPRING_DATASOURCE_USERNAME=postgres
|
||||||
@@ -31,7 +31,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:8082/api/actuator/health || exit 1"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -39,13 +39,12 @@ services:
|
|||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
args:
|
|
||||||
VITE_BASE_URL: "http://localhost:8082/api"
|
|
||||||
container_name: todo-frontend
|
container_name: todo-frontend
|
||||||
ports:
|
ports:
|
||||||
- "5173:80"
|
- "5173:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ COPY . .
|
|||||||
ARG VITE_APP_VERSION=dev
|
ARG VITE_APP_VERSION=dev
|
||||||
ENV VITE_APP_VERSION=${VITE_APP_VERSION}
|
ENV VITE_APP_VERSION=${VITE_APP_VERSION}
|
||||||
|
|
||||||
ARG VITE_BASE_URL=https://todo.almazlar.com/api
|
ARG VITE_BASE_URL=http://localhost:8080/api
|
||||||
ENV VITE_BASE_URL=${VITE_BASE_URL}
|
ENV VITE_BASE_URL=${VITE_BASE_URL}
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
Reference in New Issue
Block a user