//! Hane ve davet uç noktaları: //! - `GET /api/household` → hane bilgisi + üyeler (tüm üyeler) //! - `GET /api/household/invites` → aktif davetler (yalnızca owner) //! - `POST /api/household/invites` → yeni davet kodu (yalnızca owner) //! - `DELETE /api/household/invites/{id}` → daveti iptal et (yalnızca owner) use axum::{ Json, extract::{Path, State}, http::StatusCode, }; use mongodb::bson::{DateTime, Uuid, doc}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::{ auth::AuthUser, db::col, error::{AppError, AppResult}, models::{common::scope, household::Household, invite::Invite, user::Role}, services::invite, state::AppState, }; #[derive(Serialize, TS)] #[ts(export, rename = "Household")] pub struct HouseholdResponse { id: String, name: String, members: Vec, } #[derive(Serialize, TS)] #[ts(export, rename = "Member")] struct MemberDto { id: String, name: String, email: String, role: Role, } #[derive(Serialize, TS)] #[ts(export, rename = "Invite")] pub struct InviteDto { id: String, code: String, expires_at: String, } impl From for InviteDto { fn from(i: Invite) -> Self { Self { id: i.id.to_string(), code: i.code, expires_at: iso(i.expires_at), } } } /// Üye listesi için okunan alanlar #[derive(Deserialize)] struct MemberRow { #[serde(rename = "_id")] id: Uuid, name: String, email: String, role: Role, } pub async fn get( State(state): State, auth: AuthUser, ) -> AppResult> { let hid = auth.household_id; let mut hh_filter = scope(hid); hh_filter.insert("_id", hid); let household = state .db .collection::(col::HOUSEHOLDS) .find_one(hh_filter) .await? .ok_or(AppError::NotFound)?; let mut cursor = state .db .collection::(col::USERS) .find(scope(hid)) .projection(doc! { "name": 1, "email": 1, "role": 1 }) .sort(doc! { "created_at": 1 }) .await?; let mut members = Vec::new(); while cursor.advance().await? { let m = cursor.deserialize_current()?; members.push(MemberDto { id: m.id.to_string(), name: m.name, email: m.email, role: m.role, }); } Ok(Json(HouseholdResponse { id: hid.to_string(), name: household.name, members, })) } pub async fn list_invites( State(state): State, auth: AuthUser, ) -> AppResult>> { auth.require_owner()?; let list = invite::list_active(&state.db, auth.household_id).await?; Ok(Json(list.into_iter().map(InviteDto::from).collect())) } pub async fn create_invite( State(state): State, auth: AuthUser, ) -> AppResult<(StatusCode, Json)> { auth.require_owner()?; let inv = invite::create(&state.db, auth.household_id, auth.user_id).await?; Ok((StatusCode::CREATED, Json(inv.into()))) } pub async fn revoke_invite( State(state): State, auth: AuthUser, Path(id): Path, ) -> AppResult { auth.require_owner()?; let id = Uuid::parse_str(&id).map_err(|_| AppError::NotFound)?; invite::revoke(&state.db, auth.household_id, id).await?; Ok(StatusCode::NO_CONTENT) } fn iso(dt: DateTime) -> String { dt.try_to_rfc3339_string().unwrap_or_default() }