feat: Refactor user and thought models to use UUIDs instead of integers

- Updated user and thought models to utilize UUIDs for primary keys.
- Modified persistence functions to accommodate UUIDs for user and thought IDs.
- Implemented tag functionality with new Tag and ThoughtTag models.
- Added migration scripts to create new tables for tags and thought-tag relationships.
- Enhanced thought creation to parse hashtags and link them to thoughts.
- Updated tests to reflect changes in user and thought ID types.
This commit is contained in:
2025-09-06 15:29:38 +02:00
parent c9e99e6f23
commit b83b7acf1c
38 changed files with 638 additions and 107 deletions

View File

@@ -5,13 +5,14 @@ use axum::{
use jsonwebtoken::{decode, DecodingKey, Validation};
use once_cell::sync::Lazy;
use sea_orm::prelude::Uuid;
use serde::{Deserialize, Serialize};
use app::state::AppState;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: i32,
pub sub: Uuid,
pub exp: usize,
}
@@ -19,7 +20,7 @@ static JWT_SECRET: Lazy<String> =
Lazy::new(|| std::env::var("AUTH_SECRET").expect("AUTH_SECRET must be set"));
pub struct AuthUser {
pub id: i32,
pub id: Uuid,
}
impl FromRequestParts<AppState> for AuthUser {
@@ -31,7 +32,7 @@ impl FromRequestParts<AppState> for AuthUser {
) -> Result<Self, Self::Rejection> {
if let Some(user_id_header) = parts.headers.get("x-test-user-id") {
let user_id_str = user_id_header.to_str().unwrap_or("0");
let user_id = user_id_str.parse::<i32>().unwrap_or(0);
let user_id = user_id_str.parse::<Uuid>().unwrap_or(Uuid::nil());
return Ok(AuthUser { id: user_id });
}

View File

@@ -3,6 +3,7 @@ use axum::Router;
pub mod auth;
pub mod feed;
pub mod root;
pub mod tag;
pub mod thought;
pub mod user;
pub mod well_known;
@@ -25,6 +26,7 @@ pub fn create_router(state: AppState) -> Router {
.nest("/users", create_user_router())
.nest("/thoughts", create_thought_router())
.nest("/feed", create_feed_router())
.nest("/tags", tag::create_tag_router())
.with_state(state)
.layer(cors)
}

View File

@@ -0,0 +1,38 @@
use crate::error::ApiError;
use app::{persistence::thought::get_thoughts_by_tag_name, state::AppState};
use axum::{
extract::{Path, State},
response::IntoResponse,
routing::get,
Json, Router,
};
use models::schemas::thought::{ThoughtListSchema, ThoughtSchema};
#[utoipa::path(
get,
path = "{tagName}",
params(("tagName" = String, Path, description = "Tag name")),
responses((status = 200, description = "List of thoughts with a specific tag", body = ThoughtListSchema))
)]
async fn get_thoughts_by_tag(
State(state): State<AppState>,
Path(tag_name): Path<String>,
) -> Result<impl IntoResponse, ApiError> {
let thoughts_with_authors = get_thoughts_by_tag_name(&state.conn, &tag_name).await;
println!(
"Result from get_thoughts_by_tag_name: {:?}",
thoughts_with_authors
);
let thoughts_with_authors = thoughts_with_authors?;
println!("Thoughts with authors: {:?}", thoughts_with_authors);
let thoughts_schema: Vec<ThoughtSchema> = thoughts_with_authors
.into_iter()
.map(ThoughtSchema::from)
.collect();
println!("Thoughts schema: {:?}", thoughts_schema);
Ok(Json(ThoughtListSchema::from(thoughts_schema)))
}
pub fn create_tag_router() -> Router<AppState> {
Router::new().route("/{tag_name}", get(get_thoughts_by_tag))
}

View File

@@ -12,6 +12,7 @@ use app::{
state::AppState,
};
use models::{params::thought::CreateThoughtParams, schemas::thought::ThoughtSchema};
use sea_orm::prelude::Uuid;
use crate::{
error::ApiError,
@@ -74,7 +75,7 @@ async fn thoughts_post(
async fn thoughts_delete(
State(state): State<AppState>,
auth_user: AuthUser,
Path(id): Path<i32>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, ApiError> {
let thought = get_thought(&state.conn, id)
.await?

View File

@@ -5,6 +5,7 @@ use axum::{
routing::{get, post},
Router,
};
use sea_orm::prelude::Uuid;
use serde_json::{json, Value};
use app::persistence::{
@@ -201,7 +202,7 @@ async fn get_user_by_param(
Path(param): Path<String>,
) -> Response {
// First, try to handle it as a numeric ID.
if let Ok(id) = param.parse::<i32>() {
if let Ok(id) = param.parse::<Uuid>() {
return match get_user(&state.conn, id).await {
Ok(Some(user)) => Json(UserSchema::from(user)).into_response(),
Ok(None) => ApiError::from(UserError::NotFound).into_response(),