13 Commits

Author SHA1 Message Date
f62fb15375 NO-ISSUE build: update backend version
All checks were successful
Frontend CI / build (push) Successful in 1m26s
Deployment / deployment (release) Successful in 5m19s
Auto Comment On PR / add_improve_comment (pull_request) Successful in 12s
PR Title Check / pr-title-check (pull_request) Successful in 13s
2025-08-12 21:59:28 +08:00
a3892f2289 NO-ISSUE build: update app version
All checks were successful
Frontend CI / build (push) Successful in 1m11s
Deployment / deployment (release) Successful in 13m55s
2025-08-06 23:01:34 +08:00
71bae3d8ca BLOG-90 Intergrate error tracking with Sentry (#120)
All checks were successful
Frontend CI / build (push) Successful in 1m29s
### 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

![image.png](/attachments/22e49f8d-ac01-4d09-8ff0-7ce87b787055)

### 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>
2025-08-06 20:20:47 +08:00
a5f66616c4 BLOG-104 Implement CRUD functionality for Posts (#108)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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>
2025-08-02 14:35:27 +08:00
71528294ae BLOG-105 Implement CRUD functionality for Labels (#107)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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>
2025-08-02 10:46:00 +08:00
e255e076dc BLOG-103 Add API documentation with Utoipa (#106)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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

![image.png](/attachments/f5b4b268-f550-4d9e-9321-49a00f6b8e1a)

### 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>
2025-08-02 06:51:37 +08:00
197d7773ef BLOG-86 Checking authentication before uploading image (#101)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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>
2025-08-01 18:26:39 +08:00
9c88b4bb55 BLOG-94 Create user in DB when first login through OIDC (#96)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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>
2025-08-01 13:24:08 +08:00
dd0567c937 BLOG-85 Implement OIDC authentication (#93)
All checks were successful
Frontend CI / build (push) Successful in 1m7s
### 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>
2025-07-30 03:46:49 +08:00
ab3050db69 BLOG-78 Backend image upload and download (#84)
All checks were successful
Frontend CI / build (push) Successful in 1m4s
### 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>
2025-07-27 13:10:46 +08:00
2039edf5e9 NO-ISSUE build: update app version
All checks were successful
Frontend CI / build (push) Successful in 1m7s
Deployment / deployment (release) Successful in 7m24s
PR Title Check / pr-title-check (pull_request) Successful in 16s
2025-07-24 23:40:40 +08:00
3cb69f6e7c BLOG-45 Post content page (#67)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### 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|
|-|-|
|![image.png](/attachments/0ec5718a-f804-432f-8e4b-e9dc22c080d2)|![beta.squidspirit.com_post(iPhone 12 Pro) (1).png](/attachments/b30d1b96-d4a4-4b2b-b9bd-90fd2592ab52)|

### 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>
2025-07-24 22:20:58 +08:00
c39a800b6b BLOG-43 Post related api endpoints (#55)
All checks were successful
Frontend CI / build (push) Successful in 2m18s
### 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>
2025-06-07 21:26:10 +08:00