### Description
> [!WARNING]
> Removing `v0.1.0` version of migration from `_sqlx_migrations` table before deploying is require.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#97
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #131
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### 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
- Using `alpine` for build environment.
<https://g.co/gemini/share/ad84493a13dd>
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#122.
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #123
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
This PR updates the application to handle posts that may not have a publication date (e.g., drafts) by making the `published_time` field optional across the entire post feature stack.
This ensures that draft posts can be processed and rendered without causing errors, and prevents search engine metadata from being generated for content that is not yet published.
#### Key Changes:
* **DTO & Schema (`postInfoResponseDto.ts`):**
* The Zod schema for `PostInfoResponseSchema` has been updated to mark `published_time` as `.nullable()`.
* The `PostInfoResponseDto` class now correctly handles a `null` value from the API, mapping it to `Date | null`.
* **Domain Entity (`postInfo.ts`):**
* The core `PostInfo` entity's `publishedTime` property is now typed as `Date | null` to reflect the business logic that a post may be unpublished.
* **View Model (`postInfoViewModel.ts`):**
* Updated `publishedTime` to be `Date | null`.
* Added a new `isPublished` boolean getter for convenient conditional logic in the UI.
* The `formattedPublishedTime` getter now returns `string | null`.
* Dehydration and rehydration logic (`dehydrate`/`rehydrate`) has been updated to correctly handle the nullable `publishedTime`.
* **UI Component (`PostContentPage.svelte`):**
* The component now uses the new `isPublished` flag to conditionally render the `<StructuredData>` component for SEO. This ensures that structured data is only included for posts that have been officially published.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#118
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #121
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
#### Overview
This PR improves the website's SEO by:
1. Moving title and meta description tags from app.html to individual page components
2. Adding dynamic meta descriptions based on page content
3. Implementing structured data for blog posts using JSON-LD
4. Optimizing meta descriptions for better search engine visibility
#### Changes
- **app.html**: Removed static title and meta description tags
- **HomePage.svelte**: Added descriptive title parameter to generateTitle function
- **Terminal.svelte**: Dynamically generates meta description from terminal lines
- **PostContentPage.svelte**: Added meta description and structured data for blog posts
- **PostOverallPage.svelte**: Added descriptive meta description for blog listing page
- **StructuredData.svelte**: Created new component to generate JSON-LD structured data for blog posts
#### Benefits
- Improved SEO through better metadata management
- Enhanced search engine visibility with structured data
- More accurate and dynamic meta descriptions
- Better control over page-specific metadata
> [!NOTE]
> Since sitemap auto generating is a little more complex, it will be solved in #117 in the future.
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#48
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #116
Co-authored-by: SquidSpirit <squid@squidspirit.com>
Co-committed-by: SquidSpirit <squid@squidspirit.com>
### Description
- New env var: PUBLIC_GA_MEASUREMENT_ID=G-XXX
### Package Changes
_No response_
### Screenshots

### Reference
Resolves#112
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #113
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
- `.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
- Enhance typography and color consistency in PostPreview and PostContentPage
- Add tailwind configuration for custom typography styles
### Package Changes
_No response_
### Screenshots
_No response_
### Reference
Resolves#87
### Checklist
- [x] A milestone is set
- [x] The related issuse has been linked to this branch
Reviewed-on: #88
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>