### Description
This PR introduces full CRUD (Create, Read, Update) functionality for post labels, implemented by following the existing Clean Architecture.
#### Backend
* **New API Endpoints for Label Management:**
* `POST /label`: Create a new label (**authentication required**).
* `PUT /label/{id}`: Update a label by its ID (**authentication required**).
* `GET /label`: Get all labels.
* **Architectural Implementation:**
* **Delivery Layer**: Added `CreateLabelRequestDto`, `UpdateLabelRequestDto`, and updated `PostController` with methods to handle label-related operations.
* **Application Layer**: Created corresponding use cases (`CreateLabelUseCase`, `UpdateLabelUseCase`, `GetAllLabelsUseCase`) to handle business logic.
* **Gateway/Framework Layer**: Implemented `LabelRepository` and `LabelDbService` to manage database interactions, including creating, updating, and querying labels.
* **Route Adjustment:**
* The route for fetching all post info has been changed from `GET /post/all` to `GET /post` to be more RESTful.
#### Frontend
* **API Call Update:**
* To match the backend route change, the API path for fetching all posts is updated from `/post/all` to `/post`.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#105
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #107
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This PR integrates the **`utoipa`** and **`utoipa-redoc`** crates to automatically generate OpenAPI-compliant API documentation for the backend project.
#### Overview
To improve development efficiency and API maintainability, this change introduces `utoipa` to automate the API documentation process. By adding specific attribute macros to the source code, we can generate detailed API specifications directly and serve them through an interactive UI provided by `utoipa-redoc`.
#### Key Changes
* **Dependencies Added**
* Added `utoipa`, `utoipa-gen`, and `utoipa-redoc` to `Cargo.toml`.
* `utoipa` is used to define OpenAPI objects.
* `utoipa-redoc` is used to serve the ReDoc documentation UI.
* **Code Refactoring**
* **HTTP handler logic** in each feature (`auth`, `image`, `post`) has been extracted from the `..._web_routes.rs` files into their own dedicated files (e.g., `get_post_by_id_handler.rs`). This makes the code structure cleaner and simplifies adding documentation attributes to each handler.
* Renamed the `PostController` method from `get_full_post` to `get_post_by_id` for a more RESTful-compliant naming convention.
* **API Doc Annotation**
* Added `#[derive(ToSchema)]` or `#[derive(IntoParams)]` to all DTOs (Data Transfer Objects) so they can be recognized by `utoipa` to generate the corresponding schemas.
* Added the `#[utoipa::path]` macro to all HTTP handler functions, describing the API's path, HTTP method, tags, summary, expected responses, and security settings.
* **Doc Aggregation & Serving**
* Added an `..._api_doc.rs` file in each feature module to aggregate all API paths within that module.
* Added a new `api_doc.rs` file in the `server` crate to merge the OpenAPI documents from all features, set global information (like title, version, and the OAuth2 security scheme), and serve the documentation page on the `/redoc` route using `Redoc::with_url`.
### Package Changes
```toml
utoipa = { version = "5.4.0", features = ["actix_extras"] }
utoipa-redoc = { version = "6.0.0", features = ["actix-web"] }
```
### Screenshots

### Reference
Resolves#103
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #106
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Endpoint: GET `/me`, returns the whole user data.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#100
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #102
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This PR introduces a generic authentication middleware to protect application routes. The primary goal is to prevent unauthenticated users from uploading images.
#### Changes Implemented
* **Authentication Middleware**:
* Created a new `auth_middleware` that checks the user's session for a valid `user_id`.
* If a `user_id` exists, it's added to the request extensions, making it available to downstream handlers.
* **`UserId` Extractor**:
* A `UserId` type that implements `FromRequest` has been added.
* This allows route handlers to declaratively require authentication by simply adding `user_id: UserId` as a parameter. If the user is not logged in, the extractor automatically returns an `ErrorUnauthorized` response.
* **Route Protection**:
* The `upload_image_handler` now includes the `UserId` extractor, securing the endpoint.
* A new `/auth/me` route has been added for easily verifying the logged-in user's ID during development and testing.
* **Minor Refinements**:
* The `logout_handler` now uses `session.clear()` for more robust session termination.
* Corrected the default Redis URL from `redis://127.0.1:6379` to `redis://127.0.0.1:6379`.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#86
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #101
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- `.sqlx` should be check into version control system because it is required when rust compiling if there is no available online sql server.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
__NO_ISSUE__
### Checklist
- [x] A milestone is set
- [ ] The related issuse has been linked to this branch
Reviewed-on: #99
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- In beta environment, `v0.3.0` migration has been run, a manual revertion is required; in real environment, there is nothing to do, but to do #97 and remove migration record for `v0.1.1` manually.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#95
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #98
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This PR introduces the functionality to persist user information in the database. When a user logs in via OIDC for the first time, a new user record is created. Subsequent logins will retrieve the existing user data from the database.
This change ensures that users have a persistent identity within our system, identified by their unique combination of OIDC issuer and subject ID.
#### Key Changes
* **User Persistence Logic**:
* In `ExchangeAuthCodeUseCase`, after successfully exchanging the authorization code, the logic now checks if the user exists in our database using their `issuer` and `source_id`.
* If the user is not found (`AuthError::UserNotFound`), a new record is created in the `user` table.
* The `User` entity returned by the use case now contains the internal database `id`.
* **Database Integration in Auth Feature**:
* Introduced a new `UserDbService` trait and its `sqlx`-based implementation, `UserDbServiceImpl`, to handle database operations for users.
* The `AuthRepository` is extended to include methods for querying (`get_user_by_source_id`) and saving (`save_user`) users, delegating the calls to the new `UserDbService`.
* The dependency injection container in `server/src/container.rs` has been updated to provide the `UserDbServiceImpl` to the `AuthRepositoryImpl`.
* **Domain and Data Model Updates**:
* The `User` domain entity now includes `id` (the database primary key) and `issuer` (from OIDC claims) to uniquely identify a user across different identity providers.
* The `UserResponseDto` now exposes the internal `id` instead of the `source_id`.
* **Session Management**:
* The user's session now stores the database `user_id` (`i32`) instead of the entire user object. This is more efficient and secure.
* Session keys have been centralized into a `constants.rs` file for better maintainability.
#### Database Changes
* A new database migration has been added to create the `user` table.
* The table includes columns for `id`, `issuer`, `source_id`, `displayed_name`, and `email`.
* A **`UNIQUE` index** has been created on `(source_id, issuer)` to guarantee that each user from a specific identity provider is stored only once.
#### Refactoring
* Minor refactoring in the `image` feature to change `id: Option<i32>` to `id: i32` for consistency with the new `User` entity model.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#94
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #96
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Login with configured OIDC issuer, and then save the logged in information in server session.
- Endpoints:
- GET `/auth/login`
- GET `/auth/callback`
- GET `/auth/logout`
### Package Changes
```toml
actix-session = { version = "0.10.1", features = ["redis-session"] }
hex = "0.4.3"
openidconnect = { version = "4.0.1", features = [
"reqwest",
"reqwest-blocking",
] }
```
### Screenshots
<video src="attachments/8b15b576-61db-41b9-8587-b4b885018c93" title="Screencast From 2025-07-30 03-34-26.mp4" controls></video>
### Reference
Resolves#85
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #93
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Add some endpoints about image:
- POST `/image/upload`
- GET `/image/{id}`
> [!NOTE]
> Since there isn't identity authentication, the `/image` endpoints should be restricted to private network in nginx.
> [!NOTE]
> Volume for backend should be configured in `pod.yaml`.
### Package Changes
```toml
actix-multipart = "0.7.2"
```
### Screenshots
_No response_
### Reference
Resolves#78
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #84
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Use Qudo Merge as self-hosted PR agent
- Add a workflow that auto comment /improve command to start the agent
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#79
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #83
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Use `generateTitle` to combine app name and page title.
### Package Changes
_No response_
### Screenshots
|Home|Post Overall|Post Content|
|-|-|-|
||||
### Reference
Resolves#73
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #77
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- A a `title` attribute on the `<a>` element.
- Make the title as `<h2>` and the description as `<p>`.
### Package Changes
_No response_
### Screenshots

### Reference
Resolves #
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #76
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
<https://www.npmjs.com/package/sanitize-html>

### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#72
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #74
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
As the title.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
_NO-ISSUE_
### Checklist
- [x] A milestone is set
- [ ] The related issuse has been linked to this branch
Reviewed-on: #68
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Implement the content page
- Parse markdown formant content to html by `markdown-it`
- Use `sanitize-html` to prevent from XSS attack
- Style the html with `tailwindcss-typography`
- Fix the issue when backend parse the password to url
- Fix and make the post info list from backend always sorted by id
### Package Changes
### Rust
```toml
percent-encoding = "2.3.1"
```
### Node
```json
{
"@types/markdown-it": "^14.1.2",
"@types/sanitize-html": "^2.16.0",
"markdown-it": "^14.1.0",
"sanitize-html": "^2.17.0"
}
```
### Screenshots
|Desktop|Mobile|
|-|-|
|||
### Reference
Resolves#45
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #67
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Change the format of color response
```json
{
"red": 0,
"green": 255,
"blue": 128,
"alpha": 255
}
```
- The relationship between the label's background color and its highlight color is calculated. The method involves first converting the RGB color value to HSL, then decreasing the L (lightness) component, and finally converting it back to RGB.
### Package Changes
```json
{
"zod": "^4.0.5"
}
```
### Screenshots
|Desktop|Mobile|
|-|-|
|||
### Reference
Resolves#44
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #64
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
As the title.
### Package Changes
_No response_
### Screenshots

### Reference
Resolves#62
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #63
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
- Introduced 'HackNerdMono' font and set default body font to 'Noto Sans TC'.
- Updated HTML to include light and dark mode icons, viewport settings, and Google Fonts.
- Added logo SVGs for light and dark themes.
- Created a robots.txt file for web crawlers.
- Removed unused favicon.svg file.
### Description
- Updated deployment.yaml to specify separate build and push steps for frontend and backend.
- Added Dockerfile for backend service to define build process.
- Modified main.rs to bind the server to all network interfaces (0.0.0.0) instead of localhost.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#59
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #60
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- As the description in the issue
> - ~~Use case should be stateless~~
> > The value unwrapped from `web::Data` must be `Arc` type
> - Initializing shouldn't be done in Container
> - Rename the functions as xxx_handler in routes
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#56
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Co-authored-by: Yu Squire[ Yu, Tsung-Ying ] <squire.yu@linecorp.com>
Reviewed-on: #57
### Description
- `GET` `/post_info`
Get all the info of the posts.
- `200` Without any post
```json
[]
```
- `200` With posts
```json
[
{
"description": "This is the first post.",
"id": 1,
"labels": [
{
"color": "#FF666666",
"id": 2,
"name": "Rust"
}
],
"preview_image_url": "https://squidspirit.com/icon/logo-light.svg",
"published_time": null,
"title": "The First Post"
}
]
```
- `GET` `/post/{id}`
Get the full post content with the given `id`
- `200` With result
```json
{
"content": "Hello! I'm Squid!!",
"id": 1,
"info": {
"description": "This is the first post.",
"id": 1,
"labels": [
{
"color": "#FF666666",
"id": 2,
"name": "Rust"
}
],
"preview_image_url": "https://squidspirit.com/icon/logo-light.svg",
"published_time": null,
"title": "The First Post"
}
}
```
- `404` There is no post with the `id`
### Package Changes
```toml
[workspace.package]
version = "0.1.1"
edition = "2024"
[workspace.dependencies]
actix-web = "4.10.2"
async-trait = "0.1.88"
chrono = "0.4.41"
dotenv = "0.15.0"
env_logger = "0.11.8"
futures = "0.3.31"
log = "0.4.27"
serde = { version = "1.0.219", features = ["derive"] }
sqlx = { version = "0.8.5", features = [
"chrono",
"macros",
"postgres",
"runtime-tokio-rustls",
] }
tokio = { version = "1.45.0", features = ["full"] }
```
### Screenshots
_No response_
### Reference
Resolves#43
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #55
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
Update license so that anyone should remove my personal information before deployment.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#53
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #54
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
As the title.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
_No issue_
### Checklist
- [x] A milestone is set
- [ ] The related issuse has been linked to this branch
Reviewed-on: #42
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- As the title.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
- Resolves#36
- Solution: https://stackoverflow.com/questions/75131872/error-failed-to-solve-failed-commit-on-ref-unexpected-status-400-bad-reques
- Docker document: https://docs.docker.com/build/ci/github-actions/attestations/#max-level-provenance
> Note that adding attestations to an image means you must push the image to a registry directly, as opposed to loading the image to the local image store of the runner. This is because the local image store doesn't support loading images with attestations.
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #38
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
As the title.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
- Resolves#34
- [GitHub Action - Docker Build And Push](https://github.com/marketplace/actions/build-and-push-docker-images#customizing)
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #35
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- Create a Dockerfile to build the docker image.
- Set up the workflow to run deployment, which is to build and upload image to remote registry.
- Add a head meta html tag to show the version.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#9
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #33
Reviewed-by: zoe <zoe@noreply.localhost>
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>