From 5d030f6bf4bef05c6e6d2737c7772fdb243b3b50 Mon Sep 17 00:00:00 2001 From: carlosthe19916 <2582866+carlosthe19916@users.noreply.github.com> Date: Sun, 31 Dec 2023 13:02:27 +0100 Subject: [PATCH] Add create project code --- openubl/README.md | 2 +- openubl/api/src/system/project.rs | 82 ++++++++++++------- openubl/entity/src/project.rs | 7 +- openubl/entity/src/user_role.rs | 18 ++-- .../src/m20231223_071007_create_project.rs | 21 ++--- .../src/m20231223_075825_create_user_role.rs | 21 ++--- openubl/oidc/src/config.rs | 2 +- openubl/oidc/src/lib.rs | 9 +- openubl/server/src/lib.rs | 1 + openubl/server/src/server/project.rs | 50 +++++++---- openubl/ui/client/src/app/layout/sidebar.tsx | 2 +- openubl/ui/client/src/app/queries/projects.ts | 6 +- openubl/ui/common/src/proxies.ts | 2 +- 13 files changed, 133 insertions(+), 90 deletions(-) diff --git a/openubl/README.md b/openubl/README.md index a9374e23..547f45c1 100644 --- a/openubl/README.md +++ b/openubl/README.md @@ -7,5 +7,5 @@ docker-compose -f openubl/deploy/compose/compose.yaml up ``` ```shell -RUST_LOG=info cargo watch -x 'run -p openubl-cli -- server --db-user user --db-password password' +RUST_LOG=info cargo watch -x 'run -p openubl-cli -- server --db-user user --db-password password --oidc-auth-server-url http://localhost:9001/realms/openubl' ``` diff --git a/openubl/api/src/system/project.rs b/openubl/api/src/system/project.rs index 0a8e12a9..fc35c586 100644 --- a/openubl/api/src/system/project.rs +++ b/openubl/api/src/system/project.rs @@ -1,44 +1,68 @@ use std::fmt::{Debug, Formatter}; use sea_orm::ActiveValue::Set; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter}; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, RelationTrait, +}; +use sea_query::JoinType; +use openubl_entity as entity; use openubl_entity::project; +use openubl_entity::user_role; use crate::db::Transactional; use crate::system::error::Error; use crate::system::InnerSystem; impl InnerSystem { + pub async fn get_projects_by_user_id( + &self, + user_id: &str, + tx: Transactional<'_>, + ) -> Result, Error> { + Ok(project::Entity::find() + .join( + JoinType::InnerJoin, + user_role::Relation::Project.def().rev(), + ) + .filter(entity::user_role::Column::UserId.eq(user_id)) + .all(&self.connection(tx)) + .await? + .drain(0..) + .map(|project| (self, project).into()) + .collect()) + } + pub async fn create_project( &self, model: &project::Model, + user_id: &str, tx: Transactional<'_>, ) -> Result { - if let Some(found) = self.get_project(&model.name, tx).await? { - Ok(found) - } else { - let entity = project::ActiveModel { - name: Set(model.name.clone()), - description: Set(model.description.clone()), - sunat_username: Set(model.sunat_username.clone()), - sunat_password: Set(model.sunat_password.clone()), - sunat_factura_url: Set(model.sunat_factura_url.clone()), - sunat_guia_url: Set(model.sunat_guia_url.clone()), - sunat_percepcion_retencion_url: Set(model.sunat_percepcion_retencion_url.clone()), - }; + let project_entity = project::ActiveModel { + name: Set(model.name.clone()), + description: Set(model.description.clone()), + ..Default::default() + }; - Ok((self, entity.insert(&self.connection(tx)).await?).into()) - } + let result = project_entity.insert(&self.connection(tx)).await?; + + let user_role_entity = user_role::ActiveModel { + project_id: Set(result.id), + user_id: Set(user_id.to_string()), + role: Set(user_role::Role::Owner), + }; + user_role_entity.insert(&self.connection(tx)).await?; + + Ok((self, result).into()) } pub async fn get_project( &self, - name: &str, + id: i32, tx: Transactional<'_>, ) -> Result, Error> { - Ok(project::Entity::find() - .filter(project::Column::Name.eq(name)) + Ok(project::Entity::find_by_id(id) .one(&self.connection(tx)) .await? .map(|project| (self, project).into())) @@ -67,17 +91,13 @@ impl From<(&InnerSystem, project::Model)> for ProjectContext { } impl ProjectContext { - // pub async fn set_owner(&self, tx: Transactional<'_>) -> Result, Error> { - // Ok(advisory::Entity::find() - // .join( - // JoinType::Join, - // advisory_vulnerability::Relation::Advisory.def().rev(), - // ) - // .filter(advisory_vulnerability::Column::VulnerabilityId.eq(self.cve.id)) - // .all(&self.system.connection(tx)) - // .await? - // .drain(0..) - // .map(|advisory| (&self.system, advisory).into()) - // .collect()) - // } + pub async fn set_owner(&self, user_id: &str, tx: Transactional<'_>) -> Result<(), Error> { + let entity = user_role::ActiveModel { + project_id: Set(self.project.id), + user_id: Set(user_id.to_string()), + role: Set(user_role::Role::Owner), + }; + entity.insert(&self.system.connection(tx)).await?; + Ok(()) + } } diff --git a/openubl/entity/src/project.rs b/openubl/entity/src/project.rs index 95b0b1fc..17e1281d 100644 --- a/openubl/entity/src/project.rs +++ b/openubl/entity/src/project.rs @@ -6,14 +6,11 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize, Serialize)] #[sea_orm(table_name = "project")] pub struct Model { + #[serde(skip_deserializing)] #[sea_orm(primary_key)] + pub id: i32, pub name: String, pub description: Option, - pub sunat_username: String, - pub sunat_password: String, - pub sunat_factura_url: String, - pub sunat_guia_url: String, - pub sunat_percepcion_retencion_url: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/openubl/entity/src/user_role.rs b/openubl/entity/src/user_role.rs index 002fe7e2..c94654c1 100644 --- a/openubl/entity/src/user_role.rs +++ b/openubl/entity/src/user_role.rs @@ -6,18 +6,26 @@ use sea_orm::entity::prelude::*; #[sea_orm(table_name = "user_role")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] - pub username: String, + pub user_id: String, #[sea_orm(primary_key, auto_increment = false)] - pub project_name: i32, - pub roles: String, + pub project_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub role: Role, +} + +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(Some(1))")] +pub enum Role { + #[sea_orm(string_value = "o")] + Owner, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::project::Entity", - from = "Column::ProjectName", - to = "super::project::Column::Name", + from = "Column::ProjectId", + to = "super::project::Column::Id", on_update = "NoAction", on_delete = "Cascade" )] diff --git a/openubl/migration/src/m20231223_071007_create_project.rs b/openubl/migration/src/m20231223_071007_create_project.rs index 7fdb3960..f4b2f50b 100644 --- a/openubl/migration/src/m20231223_071007_create_project.rs +++ b/openubl/migration/src/m20231223_071007_create_project.rs @@ -12,21 +12,14 @@ impl MigrationTrait for Migration { .table(Project::Table) .if_not_exists() .col( - ColumnDef::new(Project::Name) - .string() + ColumnDef::new(Project::Id) + .integer() .not_null() + .auto_increment() .primary_key(), ) + .col(ColumnDef::new(Project::Name).string().not_null()) .col(ColumnDef::new(Project::Description).string()) - .col(ColumnDef::new(Project::SunatUsername).string().not_null()) - .col(ColumnDef::new(Project::SunatPassword).string().not_null()) - .col(ColumnDef::new(Project::SunatFacturaUrl).string().not_null()) - .col(ColumnDef::new(Project::SunatGuiaUrl).string().not_null()) - .col( - ColumnDef::new(Project::SunatPercepcionRetencionUrl) - .string() - .not_null(), - ) .to_owned(), ) .await @@ -42,11 +35,7 @@ impl MigrationTrait for Migration { #[derive(DeriveIden)] pub enum Project { Table, + Id, Name, Description, - SunatUsername, - SunatPassword, - SunatFacturaUrl, - SunatGuiaUrl, - SunatPercepcionRetencionUrl, } diff --git a/openubl/migration/src/m20231223_075825_create_user_role.rs b/openubl/migration/src/m20231223_075825_create_user_role.rs index 59372a7d..e7184e8c 100644 --- a/openubl/migration/src/m20231223_075825_create_user_role.rs +++ b/openubl/migration/src/m20231223_075825_create_user_role.rs @@ -13,18 +13,19 @@ impl MigrationTrait for Migration { Table::create() .table(UserRole::Table) .if_not_exists() - .col(ColumnDef::new(UserRole::Username).string().not_null()) - .col(ColumnDef::new(UserRole::ProjectName).string().not_null()) - .col(ColumnDef::new(UserRole::Roles).string().not_null()) + .col(ColumnDef::new(UserRole::UserId).string().not_null()) + .col(ColumnDef::new(UserRole::ProjectId).integer().not_null()) + .col(ColumnDef::new(UserRole::Role).string().not_null()) .primary_key( Index::create() - .col(UserRole::Username) - .col(UserRole::ProjectName), + .col(UserRole::UserId) + .col(UserRole::ProjectId) + .col(UserRole::Role), ) .foreign_key( ForeignKey::create() - .from_col(UserRole::ProjectName) - .to(Project::Table, Project::Name) + .from_col(UserRole::ProjectId) + .to(Project::Table, Project::Id) .on_delete(ForeignKeyAction::Cascade), ) .to_owned(), @@ -42,7 +43,7 @@ impl MigrationTrait for Migration { #[derive(DeriveIden)] pub enum UserRole { Table, - Username, - ProjectName, - Roles, + UserId, + ProjectId, + Role, } diff --git a/openubl/oidc/src/config.rs b/openubl/oidc/src/config.rs index 414aba18..f7ca80de 100644 --- a/openubl/oidc/src/config.rs +++ b/openubl/oidc/src/config.rs @@ -1,5 +1,5 @@ #[derive(clap::Args, Debug)] pub struct Oidc { - #[arg(id = "oidc-auth-server-url", long, env = "OIDC_AUTH-SERVER_URL")] + #[arg(id = "oidc-auth-server-url", long, env = "OIDC_AUTH_SERVER_URL")] pub auth_server_url: String, } diff --git a/openubl/oidc/src/lib.rs b/openubl/oidc/src/lib.rs index 21cccd12..ff747ce7 100644 --- a/openubl/oidc/src/lib.rs +++ b/openubl/oidc/src/lib.rs @@ -7,7 +7,10 @@ pub struct UserClaims { pub iss: String, pub sub: String, pub aud: String, - pub name: String, - pub email: Option, - pub email_verified: Option, +} + +impl UserClaims { + pub fn user_id(&self) -> String { + self.sub.clone() + } } diff --git a/openubl/server/src/lib.rs b/openubl/server/src/lib.rs index e05ca6c9..9e512f18 100644 --- a/openubl/server/src/lib.rs +++ b/openubl/server/src/lib.rs @@ -84,5 +84,6 @@ pub struct AppState { pub fn configure(config: &mut web::ServiceConfig) { config.service(health::liveness); config.service(health::readiness); + config.service(project::list_projects); config.service(project::create_project); } diff --git a/openubl/server/src/server/project.rs b/openubl/server/src/server/project.rs index ea831b3a..9adba320 100644 --- a/openubl/server/src/server/project.rs +++ b/openubl/server/src/server/project.rs @@ -1,35 +1,57 @@ -use actix_web::{post, web, HttpResponse, Responder}; +use actix_4_jwt_auth::AuthenticatedUser; +use actix_web::{get, post, web, HttpResponse, Responder}; use openubl_api::db::Transactional; use openubl_entity::project; +use openubl_oidc::UserClaims; use crate::server::Error; use crate::AppState; -#[utoipa::path( - responses( - (status = 200, description = "Create project"), - ), -)] -#[post("projects")] +#[utoipa::path(responses((status = 200, description = "List projects")), )] +#[get("/projects")] +pub async fn list_projects( + state: web::Data, + user: AuthenticatedUser, +) -> Result { + let projects_ctx = state + .system + .get_projects_by_user_id(&user.claims.user_id(), Transactional::None) + .await + .map_err(Error::System)?; + + Ok(HttpResponse::Ok().json( + projects_ctx + .iter() + .map(|ctx| &ctx.project) + .collect::>(), + )) +} + +#[utoipa::path(responses((status = 200, description = "Create project")))] +#[post("/projects")] pub async fn create_project( state: web::Data, json: web::Json, + user: AuthenticatedUser, ) -> Result { let prev = state .system - .get_project(&json.name, Transactional::None) - .await?; + .get_projects_by_user_id(&user.claims.user_id(), Transactional::None) + .await + .map_err(Error::System)? + .iter() + .any(|ctx| ctx.project.name == json.name); match prev { - None => { - let ctx = state + false => { + let project_ctx = state .system - .create_project(&json, Transactional::None) + .create_project(&json, &user.claims.sub, Transactional::None) .await .map_err(Error::System)?; - Ok(HttpResponse::Ok().json(ctx.project)) + Ok(HttpResponse::Ok().json(project_ctx.project)) } - Some(_) => Ok(HttpResponse::Conflict().body("Project name already exists")), + true => Ok(HttpResponse::Conflict().body("Another project has the same name")), } } diff --git a/openubl/ui/client/src/app/layout/sidebar.tsx b/openubl/ui/client/src/app/layout/sidebar.tsx index 1e3c52b0..cd5444d0 100644 --- a/openubl/ui/client/src/app/layout/sidebar.tsx +++ b/openubl/ui/client/src/app/layout/sidebar.tsx @@ -20,7 +20,7 @@ export const SidebarApp: React.FC = () => { const routeParams = useMatch("/projects/:projectId/*"); let projectId = routeParams?.params.projectId; - let { project } = useFetchProjectById(projectId || ""); + let { project } = useFetchProjectById(projectId); const renderPageNav = () => { return ( diff --git a/openubl/ui/client/src/app/queries/projects.ts b/openubl/ui/client/src/app/queries/projects.ts index eb357767..5118616a 100644 --- a/openubl/ui/client/src/app/queries/projects.ts +++ b/openubl/ui/client/src/app/queries/projects.ts @@ -27,11 +27,13 @@ export const useFetchProjects = () => { }; }; -export const useFetchProjectById = (id: number | string) => { +export const useFetchProjectById = (id?: number | string) => { const { data, isLoading, error } = useQuery({ queryKey: [ProjectsQueryKey, id], - queryFn: () => getProjectById(id), + queryFn: () => + id === undefined ? Promise.resolve(undefined) : getProjectById(id), onError: (error: AxiosError) => console.log("error", error), + enabled: id !== undefined, }); return { diff --git a/openubl/ui/common/src/proxies.ts b/openubl/ui/common/src/proxies.ts index bfd32996..c82eb261 100644 --- a/openubl/ui/common/src/proxies.ts +++ b/openubl/ui/common/src/proxies.ts @@ -3,7 +3,7 @@ import { OPENUBL_ENV } from "./environment.js"; export const proxyMap: Record = { "/hub": { - target: OPENUBL_ENV.OPENUBL_HUB_URL || "http://localhost:9002", + target: OPENUBL_ENV.OPENUBL_HUB_URL || "http://localhost:8080", logLevel: process.env.DEBUG ? "debug" : "info", changeOrigin: true,