From bf2fe07a8c73f9a13169e9e5c99f11a81983838b Mon Sep 17 00:00:00 2001 From: ToBinio Date: Thu, 7 May 2026 13:46:34 +0200 Subject: [PATCH] call rust backend from client --- Cargo.lock | 99 ++++++++++++++++++- api/Cargo.toml | 8 +- .../src/m20260503_093223_add_tag_table.rs | 2 +- .../m20260503_125344_add_category_table.rs | 2 +- .../m20260504_125311_add_basic_todo_table.rs | 2 +- api/src/auth.rs | 63 +++++++----- api/src/entities/category.rs | 2 +- api/src/entities/tag.rs | 2 +- api/src/entities/todo.rs | 2 +- api/src/main.rs | 21 +++- api/src/routes/categories.rs | 7 +- api/src/routes/tags.rs | 7 +- api/src/routes/todo.rs | 23 ++--- api/src/services/categories.rs | 12 +-- api/src/services/events.rs | 7 +- api/src/services/tags.rs | 12 +-- api/src/services/todos.rs | 20 ++-- app/plugins/api.ts | 24 +++++ app/stores/useCategoryStore.ts | 16 +-- app/stores/useTagStore.ts | 16 +-- app/stores/useTodoStore.ts | 28 +++--- app/utils/authClient.ts | 3 +- nuxt.config.ts | 1 + server/utils/auth.ts | 2 + 24 files changed, 266 insertions(+), 115 deletions(-) create mode 100644 app/plugins/api.ts diff --git a/Cargo.lock b/Cargo.lock index b094f25..2a10f0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,11 +119,13 @@ name = "api" version = "0.1.0" dependencies = [ "axum", + "axum-extra", "chrono", "color-eyre", "dotenvy", "futures", "http", + "jsonwebtoken", "migration", "reqwest 0.13.2", "sea-orm", @@ -132,6 +134,7 @@ dependencies = [ "serial_test", "tokio", "tokio-stream", + "tower", "tower-http", "tracing", "tracing-subscriber", @@ -331,6 +334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -398,6 +402,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-core", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -1315,6 +1341,30 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.1" @@ -1752,6 +1802,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "aws-lc-rs", + "base64", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2193,6 +2260,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2704,7 +2781,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -2903,7 +2980,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3302,6 +3379,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -4092,6 +4181,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/api/Cargo.toml b/api/Cargo.toml index d886f49..eb27064 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -12,7 +12,9 @@ http = "1.4.0" chrono = { version = "0.4.42", features = ["serde"] } axum = "0.8.9" -tower-http = { version = "0.6.8", features = ["trace"] } +axum-extra = { version = "0.12.6", features = ["typed-header"] } +tower = "0.5.3" +tower-http = { version = "0.6.8", features = ["cors", "trace"] } tracing-subscriber = "0.3.23" color-eyre = "0.6.5" @@ -28,6 +30,8 @@ serde_json = "1.0.149" tracing = "0.1.44" tokio-stream = { version = "0.1.18", features = ["sync"] } +reqwest = { version = "0.13.2", features = ["json", "stream"] } +jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"] } + [dev-dependencies] serial_test = "3.4.0" -reqwest = { version = "0.13.2", features = ["json", "stream"] } diff --git a/api/migration/src/m20260503_093223_add_tag_table.rs b/api/migration/src/m20260503_093223_add_tag_table.rs index ae1025c..e75cd09 100644 --- a/api/migration/src/m20260503_093223_add_tag_table.rs +++ b/api/migration/src/m20260503_093223_add_tag_table.rs @@ -14,7 +14,7 @@ impl MigrationTrait for Migration { .col(pk_uuid(Tag::Id)) .col(string(Tag::Name)) .col(string(Tag::Color)) - .col(uuid(Tag::OwnerId)) + .col(string(Tag::OwnerId)) .to_owned(), ) .await diff --git a/api/migration/src/m20260503_125344_add_category_table.rs b/api/migration/src/m20260503_125344_add_category_table.rs index 6a6694a..c6e86ef 100644 --- a/api/migration/src/m20260503_125344_add_category_table.rs +++ b/api/migration/src/m20260503_125344_add_category_table.rs @@ -15,7 +15,7 @@ impl MigrationTrait for Migration { .col(string(Category::Name)) .col(string(Category::Color)) .col(string(Category::Icon)) - .col(uuid(Category::OwnerId)) + .col(string(Category::OwnerId)) .to_owned(), ) .await diff --git a/api/migration/src/m20260504_125311_add_basic_todo_table.rs b/api/migration/src/m20260504_125311_add_basic_todo_table.rs index e171ffa..b817a95 100644 --- a/api/migration/src/m20260504_125311_add_basic_todo_table.rs +++ b/api/migration/src/m20260504_125311_add_basic_todo_table.rs @@ -14,7 +14,7 @@ impl MigrationTrait for Migration { .table(Todo::Table) .if_not_exists() .col(pk_uuid(Todo::Id)) - .col(uuid(Todo::OwnerId)) + .col(string(Todo::OwnerId)) .col(string(Todo::Title)) .col(string(Todo::Note)) .col(uuid_null(Todo::CategoryId)) diff --git a/api/src/auth.rs b/api/src/auth.rs index 226dd98..b68a401 100644 --- a/api/src/auth.rs +++ b/api/src/auth.rs @@ -1,15 +1,23 @@ -use std::str::FromStr; - -use axum::extract::FromRequestParts; +use axum::{RequestPartsExt, extract::FromRequestParts}; +use axum_extra::{ + TypedHeader, + headers::{Authorization, authorization::Bearer}, +}; use http::{StatusCode, request::Parts}; -use tracing::info; -use uuid::Uuid; +use jsonwebtoken::{DecodingKey, jwk::JwkSet}; +use serde::Deserialize; +use tracing::warn; use crate::AppState; #[derive(Clone, Debug)] pub struct User { - pub uuid: Uuid, + pub id: String, +} + +#[derive(Clone, Debug, Deserialize)] +struct Claims { + id: String, } impl FromRequestParts for User { @@ -19,21 +27,32 @@ impl FromRequestParts for User { parts: &mut Parts, _state: &AppState, ) -> Result { - //Todo: Based on JWT - let auth_header = parts - .headers - .get("Authorization") - .and_then(|h| h.to_str().ok()) - .and_then(|h| h.strip_prefix("Bearer ")) - .ok_or((StatusCode::UNAUTHORIZED, "Authorization header missing"))?; - - info!(auth_header); - - let uuid = Uuid::from_str(auth_header).map_err(|e| { - info!(?e); - (StatusCode::UNAUTHORIZED, "Invalid UUID") - })?; - - Ok(User { uuid }) + let TypedHeader(Authorization(bearer)) = parts + .extract::>>() + .await + .map_err(|_| (StatusCode::UNAUTHORIZED, "Authorization header missing"))?; + + let jwks = reqwest::get("http://localhost:3000/api/auth/jwks") + .await + .map_err(|_| (StatusCode::UNAUTHORIZED, "Failed to fetch JWKS"))? + .json::() + .await + .map_err(|_| (StatusCode::UNAUTHORIZED, "Failed to parse JWKS"))?; + + let key = DecodingKey::from_jwk(&jwks.keys.last().unwrap()) + .map_err(|_| (StatusCode::UNAUTHORIZED, "Failed to create decoding key"))?; + + let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::EdDSA); + //TODO: correctly set audience + validation.set_audience(&["http://localhost:3000"]); + + let claims: Claims = jsonwebtoken::decode(bearer.token(), &key, &validation) + .map_err(|e| { + warn!(?e); + (StatusCode::UNAUTHORIZED, "Invalid token") + })? + .claims; + + Ok(User { id: claims.id }) } } diff --git a/api/src/entities/category.rs b/api/src/entities/category.rs index 39e4020..5195c89 100644 --- a/api/src/entities/category.rs +++ b/api/src/entities/category.rs @@ -10,7 +10,7 @@ pub struct Model { pub name: String, pub color: String, pub icon: String, - pub owner_id: Uuid, + pub owner_id: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/api/src/entities/tag.rs b/api/src/entities/tag.rs index e00255e..f04bbc3 100644 --- a/api/src/entities/tag.rs +++ b/api/src/entities/tag.rs @@ -9,7 +9,7 @@ pub struct Model { pub id: Uuid, pub name: String, pub color: String, - pub owner_id: Uuid, + pub owner_id: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/api/src/entities/todo.rs b/api/src/entities/todo.rs index bcf5b65..20ef926 100644 --- a/api/src/entities/todo.rs +++ b/api/src/entities/todo.rs @@ -7,7 +7,7 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, - pub owner_id: Uuid, + pub owner_id: String, pub title: String, pub note: String, pub category_id: Option, diff --git a/api/src/main.rs b/api/src/main.rs index 97dbad6..9f71acb 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -4,7 +4,11 @@ use http::StatusCode; use migration::{Migrator, MigratorTrait}; use sea_orm::{ConnectionTrait, Database, DatabaseConnection}; use std::env; -use tower_http::trace::{self, TraceLayer}; +use tower::ServiceBuilder; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::{self, TraceLayer}, +}; use tracing::{Level, info}; use crate::services::events::EventService; @@ -41,9 +45,18 @@ async fn main() -> eyre::Result<()> { .with_state(state.clone()) .nest("/api", routes::routes(state.clone())) .layer( - TraceLayer::new_for_http() - .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) - .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), + ServiceBuilder::new() + .layer( + TraceLayer::new_for_http() + .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) + .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), + ) + .layer( + CorsLayer::new() + .allow_headers(Any) + .allow_origin(Any) + .allow_methods(Any), + ), ); let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")) diff --git a/api/src/routes/categories.rs b/api/src/routes/categories.rs index be96d7d..d8cd6d5 100644 --- a/api/src/routes/categories.rs +++ b/api/src/routes/categories.rs @@ -56,7 +56,7 @@ async fn add_category( state .event_service - .broadcast(events::Event::Category(user.uuid)); + .broadcast(events::Event::Category(user.id)); Ok(Json(new_category)) } @@ -108,7 +108,7 @@ async fn delete_category( state .event_service - .broadcast(events::Event::Category(user.uuid)); + .broadcast(events::Event::Category(user.id)); Ok(Json(deleted_category)) } @@ -116,6 +116,7 @@ async fn sse_handler( state: State, user: User, ) -> Sse>> { + let user_id = user.id.clone(); let stream = BroadcastStream::new(state.event_service.subscribe()) .filter(move |event| { let Ok(event) = event else { @@ -123,7 +124,7 @@ async fn sse_handler( }; match event { - events::Event::Category(user_uuid) => user_uuid == &user.uuid, + events::Event::Category(user_uuid) => user_uuid == &user_id, _ => false, } }) diff --git a/api/src/routes/tags.rs b/api/src/routes/tags.rs index 8332676..4265559 100644 --- a/api/src/routes/tags.rs +++ b/api/src/routes/tags.rs @@ -54,7 +54,7 @@ async fn add_tag( })?, }; - state.event_service.broadcast(events::Event::Tag(user.uuid)); + state.event_service.broadcast(events::Event::Tag(user.id)); Ok(Json(new_tag)) } @@ -92,7 +92,7 @@ async fn delete_tag( .await .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "failed to delete tag"))?; - state.event_service.broadcast(events::Event::Tag(user.uuid)); + state.event_service.broadcast(events::Event::Tag(user.id)); Ok(Json(deleted_tag)) } @@ -100,6 +100,7 @@ async fn sse_handler( state: State, user: User, ) -> Sse>> { + let user_id = user.id.clone(); let stream = BroadcastStream::new(state.event_service.subscribe()) .filter(move |event| { let Ok(event) = event else { @@ -107,7 +108,7 @@ async fn sse_handler( }; match event { - events::Event::Tag(user_uuid) => user_uuid == &user.uuid, + events::Event::Tag(user_uuid) => user_uuid == &user_id, _ => false, } }) diff --git a/api/src/routes/todo.rs b/api/src/routes/todo.rs index 82dd568..6feacb6 100644 --- a/api/src/routes/todo.rs +++ b/api/src/routes/todo.rs @@ -91,9 +91,7 @@ async fn add_todo( })?, }; - state - .event_service - .broadcast(events::Event::Todo(user.uuid)); + state.event_service.broadcast(events::Event::Todo(user.id)); Ok(Json(new_todo)) } @@ -117,9 +115,7 @@ async fn delete_todo( .await .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "failed to delete tag"))?; - state - .event_service - .broadcast(events::Event::Todo(user.uuid)); + state.event_service.broadcast(events::Event::Todo(user.id)); Ok(Json(deleted_todo)) } @@ -127,6 +123,7 @@ async fn sse_handler( state: State, user: User, ) -> Sse>> { + let user_id = user.id.clone(); let stream = BroadcastStream::new(state.event_service.subscribe()) .filter(move |event| { let Ok(event) = event else { @@ -134,7 +131,7 @@ async fn sse_handler( }; match event { - events::Event::Todo(user_uuid) => user_uuid == &user.uuid, + events::Event::Todo(user_uuid) => user_uuid == &user_id, _ => false, } }) @@ -178,9 +175,7 @@ async fn add_check( return Err((StatusCode::NOT_FOUND, "todo not found")); }; - state - .event_service - .broadcast(events::Event::Todo(user.uuid)); + state.event_service.broadcast(events::Event::Todo(user.id)); Ok(Json(checked_todo)) } @@ -200,9 +195,7 @@ async fn remove_check( return Err((StatusCode::NOT_FOUND, "todo not found")); }; - state - .event_service - .broadcast(events::Event::Todo(user.uuid)); + state.event_service.broadcast(events::Event::Todo(user.id)); Ok(Json(checked_todo)) } @@ -230,8 +223,6 @@ async fn move_todo( _ => {} } - state - .event_service - .broadcast(events::Event::Todo(user.uuid)); + state.event_service.broadcast(events::Event::Todo(user.id)); Ok(()) } diff --git a/api/src/services/categories.rs b/api/src/services/categories.rs index d7ed38a..470856e 100644 --- a/api/src/services/categories.rs +++ b/api/src/services/categories.rs @@ -17,7 +17,7 @@ impl CategoryService { db: &DatabaseConnection, ) -> Result, sea_orm::DbErr> { Category::find() - .filter(category::Column::OwnerId.eq(user.uuid)) + .filter(category::Column::OwnerId.eq(user.id.clone())) .all(db) .await .map(|categoryies| { @@ -34,7 +34,7 @@ impl CategoryService { category_id: Uuid, ) -> Result, sea_orm::DbErr> { Category::find_by_id(category_id) - .filter(category::Column::OwnerId.eq(user.uuid)) + .filter(category::Column::OwnerId.eq(user.id.clone())) .one(db) .await .map(|category| category.map(|category| category.into())) @@ -46,7 +46,7 @@ impl CategoryService { category_id: Uuid, ) -> Result { Category::delete_by_id(category_id) - .filter(category::Column::OwnerId.eq(user.uuid)) + .filter(category::Column::OwnerId.eq(user.id.clone())) .exec(db) .await } @@ -58,7 +58,7 @@ impl CategoryService { ) -> Result { Category::insert(category::ActiveModel { id: Set(category.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), name: Set(category.name), color: Set(category.color.as_str().to_string()), icon: Set(category.icon), @@ -75,12 +75,12 @@ impl CategoryService { ) -> Result { Category::update(category::ActiveModel { id: Set(category.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), name: Set(category.name), color: Set(category.color.as_str().to_string()), icon: Set(category.icon), }) - .filter(category::Column::OwnerId.eq(user.uuid)) + .filter(category::Column::OwnerId.eq(user.id.clone())) .exec(db) .await .map(|category| category.into()) diff --git a/api/src/services/events.rs b/api/src/services/events.rs index 06277e7..fdeb380 100644 --- a/api/src/services/events.rs +++ b/api/src/services/events.rs @@ -1,11 +1,10 @@ use tokio::sync::broadcast; -use uuid::Uuid; #[derive(Clone)] pub enum Event { - Tag(Uuid), - Category(Uuid), - Todo(Uuid), + Tag(String), + Category(String), + Todo(String), } #[derive(Clone)] diff --git a/api/src/services/tags.rs b/api/src/services/tags.rs index 9dc4ea6..8cf5c3e 100644 --- a/api/src/services/tags.rs +++ b/api/src/services/tags.rs @@ -17,7 +17,7 @@ impl TagService { db: &DatabaseConnection, ) -> Result, sea_orm::DbErr> { Tag::find() - .filter(tag::Column::OwnerId.eq(user.uuid)) + .filter(tag::Column::OwnerId.eq(user.id.clone())) .all(db) .await .map(|tags| tags.into_iter().map(|tag| tag.into()).collect()) @@ -29,7 +29,7 @@ impl TagService { tag_id: Uuid, ) -> Result, sea_orm::DbErr> { Tag::find_by_id(tag_id) - .filter(tag::Column::OwnerId.eq(user.uuid)) + .filter(tag::Column::OwnerId.eq(user.id.clone())) .one(db) .await .map(|tag| tag.map(|tag| tag.into())) @@ -41,7 +41,7 @@ impl TagService { tag_id: Uuid, ) -> Result { Tag::delete_by_id(tag_id) - .filter(tag::Column::OwnerId.eq(user.uuid)) + .filter(tag::Column::OwnerId.eq(user.id.clone())) .exec(db) .await } @@ -53,7 +53,7 @@ impl TagService { ) -> Result { Tag::insert(tag::ActiveModel { id: Set(tag.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), name: Set(tag.name), color: Set(tag.color.as_str().to_string()), }) @@ -69,11 +69,11 @@ impl TagService { ) -> Result { Tag::update(tag::ActiveModel { id: Set(tag.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), name: Set(tag.name), color: Set(tag.color.as_str().to_string()), }) - .filter(tag::Column::OwnerId.eq(user.uuid)) + .filter(tag::Column::OwnerId.eq(user.id.clone())) .exec(db) .await .map(|tag| tag.into()) diff --git a/api/src/services/todos.rs b/api/src/services/todos.rs index 20e92ca..fa1fd84 100644 --- a/api/src/services/todos.rs +++ b/api/src/services/todos.rs @@ -30,7 +30,7 @@ impl TodoService { db: &DatabaseConnection, ) -> Result, sea_orm::DbErr> { let todos = Todo::find() - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .order_by_asc(todo::Column::Order) .all(db) .await?; @@ -99,7 +99,7 @@ impl TodoService { id: Uuid, ) -> Result, sea_orm::DbErr> { let todo = Todo::find_by_id(id) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .one(db) .await?; @@ -145,7 +145,7 @@ impl TodoService { let todo_model = Todo::insert(todo::ActiveModel { id: Set(todo.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), title: Set(todo.title), note: Set(todo.note), category_id: Set(todo.category), @@ -156,7 +156,7 @@ impl TodoService { if let Some(reference) = reference { let target = Todo::find_by_id(reference) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .one(db) .await?; @@ -198,13 +198,13 @@ impl TodoService { ) -> Result { let updated = Todo::update(todo::ActiveModel { id: Set(todo.uuid), - owner_id: Set(user.uuid), + owner_id: Set(user.id.clone()), title: Set(todo.title.clone()), note: Set(todo.note.clone()), category_id: Set(todo.category), order: NotSet, }) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .exec(db) .await?; @@ -264,7 +264,7 @@ impl TodoService { id: Uuid, ) -> Result { Todo::delete_by_id(id) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .exec(db) .await } @@ -321,7 +321,7 @@ impl TodoService { target: Uuid, ) -> Result<(), sea_orm::DbErr> { let todo = Todo::find_by_id(todo) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .one(db) .await? .ok_or(sea_orm::DbErr::RecordNotFound( @@ -329,7 +329,7 @@ impl TodoService { ))?; let target = Todo::find_by_id(target) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .one(db) .await? .ok_or(sea_orm::DbErr::RecordNotFound( @@ -357,7 +357,7 @@ impl TodoService { Todo::update_many() .col_expr(todo::Column::Order, Expr::col(todo::Column::Order).add(1)) .filter(todo::Column::Order.gte(target)) - .filter(todo::Column::OwnerId.eq(user.uuid)) + .filter(todo::Column::OwnerId.eq(user.id.clone())) .exec(db) .await?; diff --git a/app/plugins/api.ts b/app/plugins/api.ts new file mode 100644 index 0000000..2272b94 --- /dev/null +++ b/app/plugins/api.ts @@ -0,0 +1,24 @@ +export default defineNuxtPlugin(async () => { + const config = useRuntimeConfig(); + + // TODO: handle not being logged in + const fetch = useRequestFetch(); + const data = await fetch<{ token: string }>("/api/auth/token"); + + const api = $fetch.create({ + baseURL: config.public.apiBase, + + async onRequest({ options }) { + if (data.token) { + options.headers = new Headers(options.headers); + options.headers.set("Authorization", `Bearer ${data.token}`); + } + }, + }); + + return { + provide: { + api, + }, + }; +}); diff --git a/app/stores/useCategoryStore.ts b/app/stores/useCategoryStore.ts index 4e5ac10..fb4cb8a 100644 --- a/app/stores/useCategoryStore.ts +++ b/app/stores/useCategoryStore.ts @@ -10,8 +10,8 @@ export const useCategoryStore = defineStore("categories", { async fetch() { const { showError } = useToast(); - const fetch = useRequestFetch(); - const data = await fetch(`/api/categories`).catch( + const { $api } = useNuxtApp(); + const data = await $api(`/api/categories`).catch( async (err) => { showError(err.message); return []; @@ -43,8 +43,8 @@ export const useCategoryStore = defineStore("categories", { const { showError } = useToast(); this.data = this.data.filter((label) => label.uuid !== uuid); - const fetch = useRequestFetch(); - await fetch(`/api/categories/${uuid}`, { + const { $api } = useNuxtApp(); + await $api(`/api/categories/${uuid}`, { method: "DELETE", }).catch(async (err) => { showError(err.message); @@ -62,8 +62,8 @@ export const useCategoryStore = defineStore("categories", { this.data.unshift(label); - const fetch = useRequestFetch(); - await fetch(`/api/categories`, { + const { $api } = useNuxtApp(); + await $api(`/api/categories`, { method: "POST", body: label, }).catch(async (err) => { @@ -84,8 +84,8 @@ export const useCategoryStore = defineStore("categories", { const index = this.data.findIndex((value) => value.uuid === uuid); this.data[index] = label; - const fetch = useRequestFetch(); - await fetch(`/api/categories`, { + const { $api } = useNuxtApp(); + await $api(`/api/categories`, { method: "POST", body: label, }).catch(async (err) => { diff --git a/app/stores/useTagStore.ts b/app/stores/useTagStore.ts index b0f7b8a..90916aa 100644 --- a/app/stores/useTagStore.ts +++ b/app/stores/useTagStore.ts @@ -10,8 +10,8 @@ export const useTagStore = defineStore("tags", { async fetch() { const { showError } = useToast(); - const fetch = useRequestFetch(); - const data = await fetch(`/api/tags`).catch(async (err) => { + const { $api } = useNuxtApp(); + const data = await $api(`/api/tags`).catch(async (err) => { showError(err.message); return []; }); @@ -42,8 +42,8 @@ export const useTagStore = defineStore("tags", { this.data = this.data.filter((label) => label.uuid !== uuid); - const fetch = useRequestFetch(); - await fetch(`/api/tags/${uuid}`, { + const { $api } = useNuxtApp(); + await $api(`/api/tags/${uuid}`, { method: "DELETE", }).catch(async (err) => { showError(err.message); @@ -61,8 +61,8 @@ export const useTagStore = defineStore("tags", { this.data.unshift(label); - const fetch = useRequestFetch(); - await fetch(`/api/tags`, { + const { $api } = useNuxtApp(); + await $api(`/api/tags`, { method: "POST", body: label, }).catch(async (err) => { @@ -83,8 +83,8 @@ export const useTagStore = defineStore("tags", { const index = this.data.findIndex((value) => value.uuid === uuid); this.data[index] = label; - const fetch = useRequestFetch(); - await fetch(`/api/tags`, { + const { $api } = useNuxtApp(); + await $api(`/api/tags`, { method: "POST", body: label, }).catch(async (err) => { diff --git a/app/stores/useTodoStore.ts b/app/stores/useTodoStore.ts index 6d9a002..eee5fdc 100644 --- a/app/stores/useTodoStore.ts +++ b/app/stores/useTodoStore.ts @@ -14,8 +14,8 @@ export const useTodoStore = defineStore("todos", { async fetch() { const { showError } = useToast(); - const fetch = useRequestFetch(); - const data = await fetch("/api/todos").catch(async (err) => { + const { $api } = useNuxtApp(); + const data = await $api("/api/todos").catch(async (err) => { showError(err.message); return []; }); @@ -46,8 +46,8 @@ export const useTodoStore = defineStore("todos", { this.data = this.data.filter((todo) => todo.uuid !== uuid); - const fetch = useRequestFetch(); - await fetch(`/api/todos/${uuid}`, { + const { $api } = useNuxtApp(); + await $api(`/api/todos/${uuid}`, { method: "DELETE", }).catch(async (err) => { showError(err.message); @@ -87,8 +87,8 @@ export const useTodoStore = defineStore("todos", { lastTodo?.uuid, ); - const fetch = useRequestFetch(); - await fetch("/api/todos", { + const { $api } = useNuxtApp(); + await $api("/api/todos", { method: "POST", body: { data: todo, @@ -111,8 +111,8 @@ export const useTodoStore = defineStore("todos", { const index = this.data.findIndex((value) => value.uuid === uuid); this.data[index] = todo; - const fetch = useRequestFetch(); - await fetch("/api/todos", { + const { $api } = useNuxtApp(); + await $api("/api/todos", { method: "POST", body: { data: todo, @@ -131,8 +131,8 @@ export const useTodoStore = defineStore("todos", { this.data[index] = todo; - const fetch = useRequestFetch(); - await fetch(`/api/todos/check/${uuid}`, { + const { $api } = useNuxtApp(); + await $api(`/api/todos/check/${uuid}`, { method: "POST", }).catch(async (err) => { showError(err.message); @@ -148,8 +148,8 @@ export const useTodoStore = defineStore("todos", { this.data[index] = todo; - const fetch = useRequestFetch(); - await fetch(`/api/todos/check/${uuid}`, { + const { $api } = useNuxtApp(); + await $api(`/api/todos/check/${uuid}`, { method: "DELETE", }).catch(async (err) => { showError(err.message); @@ -165,8 +165,8 @@ export const useTodoStore = defineStore("todos", { const draged = this.data.splice(dragIndex, 1)[0]!; this.data.splice(dropIndex, 0, draged); - const fetch = useRequestFetch(); - await fetch("/api/todos/move", { + const { $api } = useNuxtApp(); + await $api("/api/todos/move", { method: "POST", body: { toMove, diff --git a/app/utils/authClient.ts b/app/utils/authClient.ts index 7dc243e..1258dba 100644 --- a/app/utils/authClient.ts +++ b/app/utils/authClient.ts @@ -1,6 +1,7 @@ import { apiKeyClient } from "@better-auth/api-key/client"; import { createAuthClient } from "better-auth/vue"; +import { jwtClient } from "better-auth/client/plugins"; export const authClient = createAuthClient({ - plugins: [apiKeyClient()], + plugins: [apiKeyClient(), jwtClient()], }); diff --git a/nuxt.config.ts b/nuxt.config.ts index f33ad75..d1f94b0 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -41,6 +41,7 @@ export default defineNuxtConfig({ webpush: { publicKey: process.env.NUXT_PUBLIC_WEBPUSH_PUBLIC_KEY, }, + apiBase: process.env.NUXT_PUBLIC_API_BASE ?? "http://localhost:8080/", }, webpush: { mail: process.env.NUXT_WEBPUSH_MAIL ?? "mailto:example@example.com", diff --git a/server/utils/auth.ts b/server/utils/auth.ts index 6967bba..4dbd10e 100644 --- a/server/utils/auth.ts +++ b/server/utils/auth.ts @@ -2,6 +2,7 @@ import { DatabaseSync } from "node:sqlite"; import { apiKey } from "@better-auth/api-key"; import { betterAuth } from "better-auth"; import { testUtils } from "better-auth/plugins"; +import { jwt } from "better-auth/plugins"; export const auth = betterAuth({ database: new DatabaseSync("./.data/sqlite.db"), @@ -23,5 +24,6 @@ export const auth = betterAuth({ }, }), testUtils(), + jwt(), ], }); -- 2.51.2