13 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
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> |
|||
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> |
|||
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  ### 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> |
|||
f986810540 |
BLOG-100 User retrieval functionality in authentication module (#102)
All checks were successful
Frontend CI / build (push) Successful in 1m9s
### 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> |
|||
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> |
|||
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> |
|||
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> |
|||
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> |
|||
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| |-|-| ||| ### 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> |
|||
4a924c1b92 |
BLOG-65 Establish beta environment (#66)
All checks were successful
Frontend CI / build (push) Successful in 1m4s
### Description - Change some environment variables implementation - Nginx configuration: ```nginx server { server_name beta.squidspirit.com; proxy_pass_request_headers on; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; location / { proxy_pass http://127.0.0.1:10013/; } location /api/ { proxy_pass http://127.0.0.1:10014/; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/beta.squidspirit.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/beta.squidspirit.com/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = beta.squidspirit.com) { return 301 https://$host$request_uri; } # managed by Certbot server_name beta.squidspirit.com; listen 80; return 404; # managed by Certbot } ``` - Podman kube configuration: ```yaml apiVersion: v1 kind: Secret metadata: name: beta-blog-secret data: DATABASE_PASSWORD: {{BASE64_PASSWORD}} --- apiVersion: v1 kind: Pod metadata: name: beta-blog spec: containers: - name: postgres image: docker.io/library/postgres:17-alpine imagePullPolicy: always env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: beta-blog-secret key: DATABASE_PASSWORD volumeMounts: - name: beta-blog-postgres mountPath: /var/lib/postgresql/data - name: backend image: registry.squidspirit.com/squid/beta-blog-backend:latest imagePullPolicy: always env: - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: beta-blog-secret key: DATABASE_PASSWORD volumeMounts: - name: beta-blog-localtime mountPath: /etc/localtime readonly: true ports: - hostPort: 10014 hostIP: 127.0.0.1 containerPort: 8080 - name: frontend image: registry.squidspirit.com/squid/beta-blog-frontend:latest imagePullPolicy: always env: - name: PUBLIC_API_BASE_URL value: https://beta.squidspirit.com/api/ volumeMounts: - name: beta-blog-localtime mountPath: /etc/localtime readonly: true ports: - hostPort: 10013 hostIP: 127.0.0.1 containerPort: 3000 volumes: - name: beta-blog-localtime hostPath: path: /etc/localtime - name: beta-blog-postgres persistentVolumeClaim: claimName: beta-blog-postgres ``` ### Package Changes _No response_ ### Screenshots _No response_ ### Reference Resolves #65 ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch Reviewed-on: #66 Co-authored-by: SquidSpirit <squid@squidspirit.com> Co-committed-by: SquidSpirit <squid@squidspirit.com> |
|||
0726b11fe2 |
BLOG-59 Enhance deployment workflow and backend server configuration (#60)
All checks were successful
Frontend CI / build (push) Successful in 1m37s
### 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> |
|||
d74107a0f9 |
BLOG-56 Align clean architecture (#57)
All checks were successful
Frontend CI / build (push) Successful in 1m53s
### 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 |
|||
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> |