use nestrs::prelude::*;
use nestrs_prisma::{PrismaError, PrismaService, SortOrder};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use validator::Validate;
#[derive(Debug, Deserialize, Validate)]
pub struct CreateUserDto {
#[validate(email)]
pub email: String,
#[validate(length(min = 2, max = 80))]
pub name: String,
}
#[derive(Debug, Deserialize, Validate)]
pub struct UpdateUserDto {
#[validate(length(min = 2, max = 80))]
pub name: Option<String>,
pub active: Option<bool>,
}
#[derive(Debug, Deserialize, Validate)]
pub struct UsersPageQuery {
pub skip: Option<i64>,
pub take: Option<i64>,
}
#[derive(Debug, Deserialize, Validate)]
pub struct UserIdParam {
pub id: i64,
}
#[derive(Debug, Serialize)]
pub struct UserRow {
pub id: i64,
pub email: String,
pub name: String,
pub active: bool,
}
#[injectable]
pub struct UserService {
prisma: Arc<PrismaService>,
}
impl UserService {
pub async fn create(
&self,
tenant_id: &str,
body: CreateUserDto,
) -> Result<UserRow, HttpException> {
let row = self
.prisma
.user()
.create(UserCreateInput {
tenant_id: tenant_id.to_owned(),
email: body.email,
name: body.name,
active: true,
})
.await
.map_err(HttpException::from)?;
Ok(UserRow {
id: row.id,
email: row.email,
name: row.name,
active: row.active,
})
}
pub async fn list(
&self,
tenant_id: &str,
query: UsersPageQuery,
) -> Result<Vec<UserRow>, HttpException> {
let rows = self
.prisma
.user()
.find_many_with_options(UserFindManyOptions {
r#where: UserWhere::and(vec![user::tenant_id::equals(tenant_id.to_owned())]),
order_by: Some(vec![user::id::order(SortOrder::Asc)]),
take: Some(query.take.unwrap_or(20).clamp(1, 100)),
skip: Some(query.skip.unwrap_or(0).max(0)),
distinct: None,
})
.await
.map_err(HttpException::from)?;
Ok(rows
.into_iter()
.map(|row| UserRow {
id: row.id,
email: row.email,
name: row.name,
active: row.active,
})
.collect())
}
pub async fn update(
&self,
tenant_id: &str,
id: i64,
body: UpdateUserDto,
) -> Result<UserRow, HttpException> {
let row = self
.prisma
.user()
.update(
user::id::equals(id),
UserUpdateInput {
tenant_id: None,
email: None,
name: body.name,
active: body.active,
},
)
.await
.map_err(|e| match e {
PrismaError::RowNotFound => NotFoundException::new("user not found"),
other => HttpException::from(other),
})?;
if row.tenant_id != tenant_id {
return Err(ForbiddenException::new("user not in tenant").into());
}
Ok(UserRow {
id: row.id,
email: row.email,
name: row.name,
active: row.active,
})
}
}
#[controller(prefix = "users", version = "v1")]
pub struct UserController {
service: Arc<UserService>,
}
#[routes]
impl UserController {
#[post("/")]
async fn create(
&self,
body: ValidatedBody<CreateUserDto>,
) -> Result<Json<UserRow>, HttpException> {
let tenant_id = "org_123"; // Usually derived from auth middleware / request context.
Ok(Json(self.service.create(tenant_id, body.0).await?))
}
#[get("/")]
async fn list(
&self,
query: ValidatedQuery<UsersPageQuery>,
) -> Result<Json<Vec<UserRow>>, HttpException> {
let tenant_id = "org_123";
Ok(Json(self.service.list(tenant_id, query.0).await?))
}
#[patch("/:id")]
async fn update(
&self,
path: ValidatedPath<UserIdParam>,
body: ValidatedBody<UpdateUserDto>,
) -> Result<Json<UserRow>, HttpException> {
let tenant_id = "org_123";
Ok(Json(self.service.update(tenant_id, path.0.id, body.0).await?))
}
}