7 Commits

Author SHA1 Message Date
github-actions[bot]
90b381823a chore(release): bump version to v0.0.3 and update changelog [skip ci] 2026-02-21 08:14:22 +00:00
almazlar
52f29038d8 feat: add custom favicon and update page title
All checks were successful
Release and Build Docker Images / release-and-build (push) Successful in 1m28s
2026-02-21 11:14:10 +03:00
github-actions[bot]
5dabf3f4ca chore(release): bump version to v0.0.2 and update changelog [skip ci] 2026-02-21 08:03:55 +00:00
almazlar
281004434d feat: Implement responsive styling for various screen sizes and refactor the main todo list container class.
All checks were successful
Release and Build Docker Images / release-and-build (push) Successful in 1m29s
2026-02-21 11:03:18 +03:00
github-actions[bot]
c8b96ab8e7 chore(release): bump version to v0.0.1 and update changelog [skip ci] 2026-02-21 07:41:54 +00:00
almazlar
37ae9dbe53 feat: Introduce application version display in the frontend footer, showing both frontend and backend versions with integrated build arguments.
All checks were successful
Release and Build Docker Images / release-and-build (push) Successful in 1m29s
2026-02-21 10:34:08 +03:00
almazlar
bbd3beb22a feat: Automate versioning, changelog updates, and Git tagging, using the generated version for Docker image tags. 2026-02-21 10:28:10 +03:00
12 changed files with 313 additions and 13 deletions

View File

@@ -1,4 +1,4 @@
name: Build and Push Docker Images name: Release and Build Docker Images
on: on:
push: push:
@@ -12,11 +12,92 @@ env:
OWNER: ${{ gitea.repository_owner }} OWNER: ${{ gitea.repository_owner }}
jobs: jobs:
build-and-push: release-and-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.ACCESS_TOKEN }}
- name: Git Configuration
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Generate Version and Changelog
id: generate
run: |
# 1. Fetch the latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $LATEST_TAG"
# 2. Calculate the next patch version
if [[ $LATEST_TAG == v* ]]; then
VERSION_NUM=${LATEST_TAG:1}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_NUM"
PATCH=$((PATCH + 1))
NEW_TAG="v$MAJOR.$MINOR.$PATCH"
else
# Fallback if the tag format is unrecognized
NEW_TAG="v0.0.1"
fi
echo "New tag: $NEW_TAG"
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
# 3. Create a temporary file for the new changelog entry
TEMP_CHANGELOG=$(mktemp)
DATE=$(date +'%Y-%m-%d')
echo "## [$NEW_TAG] - $DATE" > "$TEMP_CHANGELOG"
echo "" >> "$TEMP_CHANGELOG"
# Fetch all commit messages since the latest tag to HEAD
if [ "$LATEST_TAG" = "v0.0.0" ]; then
git log --pretty=format:"* %s (%h)" >> "$TEMP_CHANGELOG"
else
git log ${LATEST_TAG}..HEAD --pretty=format:"* %s (%h)" >> "$TEMP_CHANGELOG"
fi
echo "" >> "$TEMP_CHANGELOG"
echo "" >> "$TEMP_CHANGELOG"
# 4. Prepend the new entry to the existing CHANGELOG.md
if [ -f CHANGELOG.md ]; then
cat CHANGELOG.md >> "$TEMP_CHANGELOG"
fi
mv "$TEMP_CHANGELOG" CHANGELOG.md
echo "Changelog generated successfully."
- name: Commit and Push Changes
run: |
NEW_TAG=${{ steps.generate.outputs.new_tag }}
# Add the updated changelog
git add CHANGELOG.md
# Only commit if there are changes
if ! git diff-index --quiet HEAD; then
git commit -m "chore(release): bump version to $NEW_TAG and update changelog [skip ci]"
# Create the new tag
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
# Push the commit and the new tag
git push origin main
git push origin "$NEW_TAG"
else
echo "No changes to commit."
# If we haven't already tagged this commit, do it now
if ! git ls-remote --tags origin | grep -q "refs/tags/$NEW_TAG"; then
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin "$NEW_TAG"
fi
fi
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -35,7 +116,7 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.OWNER }}/todo-backend images: ${{ env.REGISTRY }}/${{ env.OWNER }}/todo-backend
tags: | tags: |
type=raw,value=latest type=raw,value=latest
type=sha,prefix={{branch}}- type=raw,value=${{ steps.generate.outputs.new_tag }}
- name: Build and push Backend image - name: Build and push Backend image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@@ -44,6 +125,8 @@ jobs:
push: true push: true
tags: ${{ steps.meta-backend.outputs.tags }} tags: ${{ steps.meta-backend.outputs.tags }}
labels: ${{ steps.meta-backend.outputs.labels }} labels: ${{ steps.meta-backend.outputs.labels }}
build-args: |
APP_VERSION=${{ steps.generate.outputs.new_tag }}
- name: Extract metadata (tags, labels) for Frontend - name: Extract metadata (tags, labels) for Frontend
id: meta-frontend id: meta-frontend
@@ -52,7 +135,7 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.OWNER }}/todo-frontend images: ${{ env.REGISTRY }}/${{ env.OWNER }}/todo-frontend
tags: | tags: |
type=raw,value=latest type=raw,value=latest
type=sha,prefix={{branch}}- type=raw,value=${{ steps.generate.outputs.new_tag }}
- name: Build and push Frontend image - name: Build and push Frontend image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@@ -61,3 +144,5 @@ jobs:
push: true push: true
tags: ${{ steps.meta-frontend.outputs.tags }} tags: ${{ steps.meta-frontend.outputs.tags }}
labels: ${{ steps.meta-frontend.outputs.labels }} labels: ${{ steps.meta-frontend.outputs.labels }}
build-args: |
VITE_APP_VERSION=${{ steps.generate.outputs.new_tag }}

22
CHANGELOG.md Normal file
View File

@@ -0,0 +1,22 @@
## [v0.0.3] - 2026-02-21
* feat: add custom favicon and update page title (52f2903)
## [v0.0.2] - 2026-02-21
* feat: Implement responsive styling for various screen sizes and refactor the main todo list container class. (2810044)
## [v0.0.1] - 2026-02-21
* feat: Introduce application version display in the frontend footer, showing both frontend and backend versions with integrated build arguments. (37ae9db)
* feat: Automate versioning, changelog updates, and Git tagging, using the generated version for Docker image tags. (bbd3beb)
* feat: Add `https://todo.almazlar.com` to allowed CORS origins. (a606983)
* refactor: Update TodoController base request mapping from `/todos` to `/api/todos`. (e8be570)
* refactor: remove `/api` prefix from TodoController request mapping. (d118222)
* feat: Broaden backend CORS mapping to all paths and update frontend API base URL to production. (ca838b1)
* refactor: switch to using docker/login-action for container registry authentication (568f662)
* build: Update Gitea workflow to use `docker login` with `password-stdin` instead of `docker/login-action`. (dd0c2e2)
* ci: Replace github context variables with gitea context variables in Docker build workflow. (082abfc)
* feat: Add initial frontend dependencies, base CSS, and Gitea Docker build workflow. (cf58ccb)
* Initial commit (c8572c5)

View File

@@ -8,6 +8,8 @@ RUN mvn clean package -DskipTests
# Run stage # Run stage
FROM eclipse-temurin:25-jre FROM eclipse-temurin:25-jre
WORKDIR /app WORKDIR /app
ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
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"]

View File

@@ -0,0 +1,25 @@
package com.todo.backend.controller;
import org.springframework.beans.factory.annotation.Value;
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.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/version")
public class VersionController {
@Value("${APP_VERSION:dev}")
private String appVersion;
@GetMapping
public ResponseEntity<Map<String, String>> getVersion() {
Map<String, String> response = new HashMap<>();
response.put("version", appVersion);
return ResponseEntity.ok(response);
}
}

View File

@@ -4,6 +4,8 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY . . COPY . .
ARG VITE_APP_VERSION=dev
ENV VITE_APP_VERSION=${VITE_APP_VERSION}
RUN npm run build RUN npm run build
# Run stage # Run stage

View File

@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title> <title>To-Do Tasks</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="4" stroke="none" fill="url(#grad)" />
<path d="M8 12.5l2.5 2.5 5.5-5.5" stroke="#ffffff" stroke-width="2.5" />
<defs>
<linearGradient id="grad" x1="3" y1="3" x2="21" y2="21" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8b5cf6" />
<stop offset="100%" stop-color="#ec4899" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 557 B

View File

@@ -1,4 +1,7 @@
.app-container { .app-container {
display: flex;
flex-direction: column;
min-height: 90vh;
width: 100%; width: 100%;
max-width: 600px; max-width: 600px;
background: var(--panel-bg); background: var(--panel-bg);
@@ -11,6 +14,27 @@
animation: slideIn 0.5s ease-out; animation: slideIn 0.5s ease-out;
} }
.main-content {
flex-grow: 1;
}
.app-footer {
margin-top: 2rem;
padding-top: 1rem;
text-align: center;
color: var(--text-muted);
font-size: 0.85em;
border-top: 1px solid var(--border-color);
}
.app-footer code {
background: rgba(139, 92, 246, 0.1);
padding: 0.2rem 0.4rem;
border-radius: 6px;
margin: 0 0.2rem;
color: var(--accent-primary);
}
@keyframes slideIn { @keyframes slideIn {
from { from {
opacity: 0; opacity: 0;
@@ -30,6 +54,7 @@
margin-bottom: 2rem; margin-bottom: 2rem;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
@@ -184,3 +209,89 @@
.error { .error {
color: var(--danger); color: var(--danger);
} }
/* --- Responsive Design --- */
/* Tablets and small desktops */
@media (max-width: 768px) {
.app-container {
padding: 1.75rem;
width: 90%;
max-width: 600px;
min-height: 80vh;
}
.title {
font-size: 2.25rem;
}
}
/* Mobile phones */
@media (max-width: 480px) {
.app-container {
padding: 1.5rem 1rem;
border-radius: 0;
margin: 0;
width: 100%;
max-width: 100%;
min-height: 100vh;
border: none;
box-shadow: none;
display: flex;
flex-direction: column;
}
.main-content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.todo-wrapper {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.todo-list {
flex-grow: 1;
}
.title {
font-size: 1.75rem;
margin-bottom: 1.25rem;
}
.todo-form {
flex-direction: column;
gap: 0.75rem;
}
.add-btn {
padding: 1rem;
width: 100%;
font-size: 1.05rem;
justify-content: center;
}
.todo-item {
padding: 0.85rem 0.75rem;
border-radius: 10px;
}
.todo-text {
font-size: 1rem;
}
.app-footer {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 2rem;
padding-bottom: 1rem;
}
.app-footer code {
display: inline-block;
}
}

View File

@@ -1,12 +1,34 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import TodoList from './components/TodoList'; import TodoList from './components/TodoList';
import { getVersion } from './services/api';
import './App.css'; import './App.css';
function App() { function App() {
const [backendVersion, setBackendVersion] = useState('...');
const frontendVersion = import.meta.env.VITE_APP_VERSION || 'dev';
useEffect(() => {
const fetchVersion = async () => {
try {
const data = await getVersion();
setBackendVersion(data.version || 'dev');
} catch (error) {
console.error('Error fetching backend version:', error);
setBackendVersion('unknown');
}
};
fetchVersion();
}, []);
return ( return (
<main> <div className="app-container">
<TodoList /> <main className="main-content">
</main> <TodoList />
</main>
<footer className="app-footer">
<p>Frontend <code>{frontendVersion}</code> | Backend <code>{backendVersion}</code></p>
</footer>
</div>
); );
} }

View File

@@ -73,7 +73,7 @@ const TodoList = () => {
}; };
return ( return (
<div className="app-container"> <div className="todo-wrapper">
<h1 className="title">Tasks</h1> <h1 className="title">Tasks</h1>
<form className="todo-form" onSubmit={handleCreate}> <form className="todo-form" onSubmit={handleCreate}>

View File

@@ -22,7 +22,7 @@ body {
font-family: var(--font-family); font-family: var(--font-family);
background-color: var(--bg-color); background-color: var(--bg-color);
background-image: radial-gradient(circle at top right, rgba(139, 92, 246, 0.15), transparent 40%), background-image: radial-gradient(circle at top right, rgba(139, 92, 246, 0.15), transparent 40%),
radial-gradient(circle at bottom left, rgba(236, 72, 153, 0.15), transparent 40%); radial-gradient(circle at bottom left, rgba(236, 72, 153, 0.15), transparent 40%);
color: var(--text-main); color: var(--text-main);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
@@ -31,6 +31,19 @@ body {
padding: 4rem 1rem; padding: 4rem 1rem;
} }
@media (max-width: 768px) {
body {
padding: 2rem 1rem;
}
}
@media (max-width: 480px) {
body {
padding: 0;
align-items: flex-start;
}
}
button { button {
cursor: pointer; cursor: pointer;
font-family: inherit; font-family: inherit;

View File

@@ -33,3 +33,11 @@ export const deleteTodo = async (id) => {
if (!response.ok) throw new Error('Failed to delete todo'); if (!response.ok) throw new Error('Failed to delete todo');
return true; return true;
}; };
export const getVersion = async () => {
// Use the origin to ensure it accesses the /api/version correctly regardless of environment
const VERSION_URL = BASE_URL.replace('/todos', '/version');
const response = await fetch(VERSION_URL);
if (!response.ok) throw new Error('Failed to fetch version');
return response.json();
};