### Description
The relationship between `is_published_only` and `has_logged_in`:
| is_published_only | has_logged_in | result |
| ----------------- | ------------- | ------ |
| T | T | T |
| T | F | T |
| F | T | F |
| F | F | T |
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#128
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #129
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This PR introduces an authorization layer for the post feature. It ensures that create, update, and read operations for posts are properly controlled based on user authentication status and post visibility (published vs. unpublished).
#### Key Changes:
* **Restricted Access to Unpublished Posts**:
* Unauthenticated users can no longer access unpublished posts via the `GET /post/{id}` endpoint. Attempting to do so will now result in an `HTTP 401 Unauthorized` error.
* The `get_all_post_info` endpoint is now aware of the user's authentication status to correctly filter posts.
* **Authentication Required for Modifications**:
* Creating (`POST /post`) and updating (`PUT /post/{id}`) posts now requires an authenticated user. The `user_id` is passed from the web handler through the controller to the use cases.
* **New Error Type**:
* A new `PostError::Unauthorized` variant has been added to handle access control failures gracefully.
* **API & Core Logic Updates**:
* The `PostController`, use cases (`GetFullPostUseCase`, `GetAllPostInfoUseCase`, etc.), and web handlers have been updated to accept and process the `user_id`.
* The `GetFullPostUseCase` now contains the primary logic to prevent unauthenticated access to draft posts.
* OpenAPI (Utopia) documentation has been updated to reflect these new authorization rules.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#119
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #124
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
There are several environment variables should be set:
- Frontend
- `PUBLIC_SENTRY_DSN`
- `SENTRY_AUTH_TOKEN`
- Backend
- `SENTRY_DSN`
If the dsn isn't set, errors won't be sent to Sentry.
### Package Changes
_No response_
### Screenshots

### Reference
Resolves#90
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #120
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This pull request introduces the core functionality for creating and updating posts, completing the backend CRUD operations for the `post` feature. It includes new API endpoints, database schema changes, and corresponding updates across the entire application stack from the database layer to the frontend.
#### Backend API
- **Added new authenticated endpoints:**
- `POST /post`: To create a new post.
- `PUT /post/{id}`: To update an existing post.
- Implemented the full vertical slice for these operations, including:
- `CreatePostUseCase` and `UpdatePostUseCase`.
- Repository and DB service methods for creating, updating, and associating posts with labels.
- Transactional database operations to ensure data integrity when creating/updating posts and their associated labels.
#### Database
- Added a new migration to include an `"order"` column in the `post_label` table.
- This column preserves the user-defined order of labels for each post.
- Queries have been updated to fetch and sort labels based on this new column.
#### API Schema & Documentation
- Enhanced `utoipa` OpenAPI documentation with more specific formats for data types:
- `#[schema(format = Uri)]` for URLs like `preview_image_url`.
- `#[schema(format = Email)]` for user emails.
- `#[schema(format = DateTime)]` for timestamps.
- Standardized the `published_time` field to use the RFC3339 string format instead of a numeric timestamp, improving API clarity and interoperability.
#### Frontend
- Updated the `PostInfoResponseDto` in the frontend to correctly parse the new `DateTime` (ISO string) format for `published_time`.
#### Refactoring
- Renamed `get_full_post` to a more descriptive `get_post_by_id` across the post feature module for better code clarity.
### Package Changes
```toml
utoipa = { version = "5.4.0", features = [
"actix_extras",
"non_strict_integers",
"url",
] }
```
### Screenshots
_No response_
### Reference
Resolves#104
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #108
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### 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
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
- 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 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>