use actix_web::{HttpResponse, web}; use serde::Deserialize; use utoipa::IntoParams; use crate::api::response::{ApiErrorResponse, ApiResponse}; use crate::error::AppError; use crate::service::AppService; use crate::service::user::security::{ CreatePersonalAccessTokenParams, CreatePersonalAccessTokenResponse, }; use crate::session::Session; #[derive(Debug, Deserialize, IntoParams, utoipa::ToSchema)] pub struct CreateTokenBody { /// Display name for the token (e.g., "My CLI Token") pub name: String, /// List of permission scopes assigned to the token pub scopes: Vec, /// Optional expiration date (UTC). If not set, the token never expires. pub expires_at: Option>, } /// Create a personal access token /// /// Creates a new personal access token (PAT) for the authenticated user. /// The full token value is returned in the response — this is the ONLY time it will be shown. /// Store it securely; it cannot be retrieved again. /// Requires authentication. #[utoipa::path( post, path = "/api/v1/user/security/tokens", tag = "User", operation_id = "userCreateToken", params(CreateTokenBody), responses( (status = 201, description = "Personal access token created successfully. The raw token value is included in the response and will never be shown again.", body = ApiResponse), (status = 400, description = "Invalid request body (e.g., missing name or scopes)", body = ApiErrorResponse), (status = 401, description = "Authentication required or session expired", body = ApiErrorResponse), (status = 500, description = "Internal server error", body = ApiErrorResponse), ), security( ("session_cookie" = []) ) )] pub async fn create_token( service: web::Data, session: Session, body: web::Json, ) -> Result { let params = CreatePersonalAccessTokenParams { name: body.name.clone(), scopes: body.scopes.clone(), expires_at: body.expires_at, }; let token = service .user .user_create_personal_access_token(&session, params) .await?; Ok(HttpResponse::Created().json(ApiResponse::new(token))) }