diff --git a/docs/DDD.puml b/docs/DDD.puml index 5f5d440..fe0e1fa 100644 --- a/docs/DDD.puml +++ b/docs/DDD.puml @@ -187,105 +187,124 @@ rectangle "Kleff Hosting Domain" as Domain #line.dashed { WorkspaceMember "*" --> "1" ID : userId } - ' ================================== - ' Project Management BC + ' ================================== + ' Project Management BC (UPDATED PER SQL SCHEMA) ' ================================== package "Project Management BC\n(Project Lifecycle & Collaboration)" #D1C4E9 { AGGREGATE_ROOT(Project) #lightblue { - projectId: ProjectIdentifier - workspaceId: WorkspaceIdentifier + projectId: UUID name: String - description: String - ownerId: ID - repositoryUrl: String - branch: String - dockerComposePath: String - environmentVariables: Map - status: ProjectStatus - createdAt: DateTime - updatedAt: DateTime + description: Text + ownerId: String + environmentVariables: JSONB + projectStatus: String + createdDate: Timestamp + updatedDate: Timestamp + } + + AGGREGATE(Invitation) #lightblue { + id: Integer + projectId: String + inviterId: String + inviteeEmail: String + role: RoleEnum + status: InvitationStatus + expiresAt: Timestamp + createdAt: Timestamp + updatedAt: Timestamp } ENTITY(ProjectCollaborator) #lightblue { - collaboratorId: CollaboratorIdentifier - projectId: ProjectIdentifier - userId: ID - role: CollaboratorRole - permissions: Set - invitedBy: ID - invitedAt: DateTime - acceptedAt: DateTime - } - - VALUE_OBJECT(ProjectIdentifier) #Bisque { - projectId: UUID + id: Integer + projectId: String + userId: String + role: RoleEnum + status: CollaboratorStatus + invitedBy: String + invitedAt: Timestamp + acceptedAt: Timestamp + expiresAt: Timestamp + lastAccessedAt: Timestamp + createdAt: Timestamp + updatedAt: Timestamp + } + + ENTITY(FeatureFlag) #lightgrey { + id: Integer + flagKey: String + enabled: Boolean + description: Text } - VALUE_OBJECT(CollaboratorIdentifier) #Bisque { - collaboratorId: UUID + ENTITY(AuthorizationAuditLog) #lightgrey { + id: BigInt + userId: String + projectId: String + action: String + resourceType: String + resourceId: String + permissionChecked: String + authorizationResult: AuthResultEnum + shadowMode: Boolean + ipAddress: String + userAgent: Text + requestId: String + changes: JSON + createdAt: Timestamp } - ENUM(ProjectStatus) #SandyBrown { - ACTIVE, - PAUSED, - ARCHIVED, - DELETED + ENUM(RoleEnum) #SandyBrown { + OWNER + ADMIN + DEVELOPER + VIEWER } - ENUM(CollaboratorRole) #SandyBrown { - OWNER, - ADMIN, - DEVELOPER, - VIEWER + ENUM(InvitationStatus) #SandyBrown { + PENDING + ACCEPTED + EXPIRED } - ENUM(ProjectPermission) #SandyBrown { - READ_PROJECT, - WRITE_PROJECT, - DEPLOY, - MANAGE_ENV_VARS, - VIEW_LOGS, - VIEW_METRICS, - MANAGE_COLLABORATORS, - DELETE_PROJECT, - MANAGE_BILLING + ENUM(CollaboratorStatus) #SandyBrown { + PENDING + ACCEPTED + REFUSED + EXPIRED } - VALUE_OBJECT(GitWebhook) #Bisque { - webhookUrl: String - secret: String - enabled: Boolean + ENUM(AuthResultEnum) #SandyBrown { + ALLOW + DENY + SHADOW_ALLOW + SHADOW_DENY } note right of Project - Invariant: - - Must have exactly one OWNER - - Repository URL must be valid Git URL - - Name must be unique per owner - - Docker Compose path must be valid + Invariants: + - Project name is NOT NULL + - Uses JSONB for environment variables end note note right of ProjectCollaborator - Invariant: - - OWNER has all permissions - - Cannot remove last OWNER - - User cannot be added twice to same project - - Permissions must match role constraints + Invariants: + - Unique constraint on (projectId, userId) + - Tracks last access for security auditing end note - Project "1" o--> "1" ProjectIdentifier - Project "1" --> "*" ProjectCollaborator : collaborators - Project "1" --> "1" ProjectStatus : status - Project "1" --> "1" ID : ownerId - Project "1" --> "0..1" GitWebhook : webhook - - ProjectCollaborator "*" --> "1" WorkspaceIdentifier : viaProjectWorkspace - ProjectCollaborator "1" o--> "1" CollaboratorIdentifier - ProjectCollaborator "*" --> "1" Project : belongsTo - ProjectCollaborator "1" --> "1" ID : userId - ProjectCollaborator "1" --> "1" CollaboratorRole : role - ProjectCollaborator "*" --> "*" ProjectPermission : permissions + Project "1" *-- "*" ProjectCollaborator : consists of + Project "1" *-- "*" Invitation : tracks + ProjectCollaborator --> RoleEnum + Invitation --> RoleEnum + Invitation --> InvitationStatus + ProjectCollaborator --> CollaboratorStatus + AuthorizationAuditLog --> AuthResultEnum + + ' Links to Identity + Project --> ID : ownerId + ProjectCollaborator --> ID : userId + Invitation --> ID : inviterId } ' ================================== diff --git a/docs/Project/C4L3-Project.puml b/docs/Project/C4L3-Project.puml index 984fb8f..1382d84 100644 --- a/docs/Project/C4L3-Project.puml +++ b/docs/Project/C4L3-Project.puml @@ -8,7 +8,7 @@ title Level 3 - Component Diagram for Project Management Service (Project Lifecy ' ==== External Containers / Systems (from C4L2) ==== Container(spa, "Kleff Dashboard (SPA)", "TypeScript, React", "Frontend for project owners & collaborators.") Container(identity_svc, "Identity & Access Service", "Java/Go", "Manages users, roles, and tenants.") -ContainerDb(project_db, "project-db", "PostgreSQL", "Stores Projects, Collaborators, permissions, and webhook configuration.") +ContainerDb(project_db, "project-db", "PostgreSQL", "Stores Projects, Invitations, Collaborators, Feature Flags, and Audit Logs.") System_Ext(email_service, "Email Service", "SMTP / Email API", "Sends collaboration invitations and notifications.") System_Ext(git_provider, "Git Provider", "GitHub/GitLab", "Hosts repositories and webhooks.") @@ -35,9 +35,18 @@ Container_Boundary(project_svc, "Project Management Service") { Component(collab_domain, "ProjectCollaborator Entity", "Domain Model", "Represents membership of a user in a project with a role and fine-grained permissions.") - ' ---- Infrastructure / Adapters ---- - Component(project_repo, "ProjectRepository", "Repository Adapter", "Persists and retrieves Project aggregates and collaborator entities from project-db.") + ' ---- Infrastructure / Adapters (Repositories) ---- + Component(project_repo, "ProjectRepository", "Repository Adapter", "Persists and retrieves Project aggregates (projects table).") + Component(collab_repo, "CollaboratorRepository", "Repository Adapter", "Manages project membership and unique constraints (collaborators table).") + + Component(invite_repo, "InvitationRepository", "Repository Adapter", "Handles the lifecycle of project invites (invitations table).") + + Component(feature_flag_repo, "FeatureFlagRepository", "Repository Adapter", "Retrieves authorization toggle states (feature_flags table).") + + Component(audit_repo, "AuthorizationAuditRepository", "Repository Adapter", "Records authorization decisions and changes (authorization_audit_logs table).") + + ' ---- Infrastructure / Adapters (Clients) ---- Component(identity_client, "IdentityServiceClient", "Integration Adapter", "Looks up users by ID/email and validates their existence in the Identity & Access BC.") Component(email_adapter, "ProjectNotification", "Integration Adapter", "Sends collaboration invitations and project notifications via the application") @@ -64,16 +73,24 @@ Rel(project_app_svc, project_repo, "Creates/updates Projects & webhook config", Rel(project_app_svc, project_domain, "Applies domain logic", "Method calls") Rel(project_app_svc, git_integration, "Configures webhooks on repository", "HTTPS/API") -Rel(collab_app_svc, project_repo, "Updates collaborator roles & permissions", "Repository API") +Rel(collab_app_svc, collab_repo, "Updates collaborator roles & permissions", "Repository API") +Rel(collab_app_svc, invite_repo, "Creates/Updates invitations", "Repository API") Rel(collab_app_svc, collab_domain, "Applies collaboration domain rules", "Method calls") Rel(collab_app_svc, identity_client, "Resolves invitee user IDs", "JSON/REST/HTTPS") Rel(collab_app_svc, email_adapter, "Sends invitations to collaborators", "Send email") -Rel(project_permission_svc, project_repo, "Reads project collaborators & permissions", "Repository API") +Rel(project_permission_svc, collab_repo, "Reads project collaborators & permissions", "Repository API") +Rel(project_permission_svc, feature_flag_repo, "Checks shadow/enforce modes", "Repository API") +Rel(project_permission_svc, audit_repo, "Logs auth decisions", "Repository API") Rel(project_permission_svc, collab_domain, "Evaluates project-level permissions", "Method calls") ' ==== Adapters -> External Systems ==== -Rel(project_repo, project_db, "Reads/Writes Projects & Collaborators", "SQL") +Rel(project_repo, project_db, "Reads/Writes projects", "SQL") +Rel(collab_repo, project_db, "Reads/Writes collaborators", "SQL") +Rel(invite_repo, project_db, "Reads/Writes invitations", "SQL") +Rel(feature_flag_repo, project_db, "Reads feature_flags", "SQL") +Rel(audit_repo, project_db, "Writes authorization_audit_logs", "SQL") + Rel(identity_client, identity_svc, "Reads User info from Identity & Access BC", "JSON/REST/HTTPS") Rel(git_integration, git_provider, "Configures webhooks & repo metadata", "HTTPS/Git API") @@ -101,4 +118,4 @@ Rel(git_integration, git_provider, "Configures webhooks & repo metadata", "HTTPS ' - DELETE /api/projects/{projectId}/permissions/grants/{grantId} -@enduml +@enduml \ No newline at end of file diff --git a/docs/Project/ProjectManagementUseCase.puml b/docs/Project/ProjectManagementUseCase.puml index b00fd33..7dcee60 100644 --- a/docs/Project/ProjectManagementUseCase.puml +++ b/docs/Project/ProjectManagementUseCase.puml @@ -36,7 +36,6 @@ Collab -- UC14 Dev -- UC15 ' Internal relationships -UC10 ..> UC11 : <> UC12 ..> UC13 : <> UC14 ..> UC13 : <> diff --git a/docs/Project/UC10-DLCD.puml b/docs/Project/UC10-DLCD.puml new file mode 100644 index 0000000..29bacd5 --- /dev/null +++ b/docs/Project/UC10-DLCD.puml @@ -0,0 +1,42 @@ +@startuml +title UC-01: Create Project - Design-Level Class Diagram (DLCD) + +class Project { + - UUID id + - String name + - String repositoryUrl + - ProjectStatus status + + validate() + + updateSettings() +} + +enum ProjectStatus { + CREATING + ACTIVE + ARCHIVED +} + +interface ProjectRepository { + + save(Project): Project + + findByName(String): Project +} + +class ProjectApplicationService { + + createProject(DTO): Project +} + +class ProjectApiController { + + createProject(HttpRequest): HttpResponse +} + +class GitIntegrationAdapter { + + configureWebhooks(String): String +} + +ProjectApiController --> ProjectApplicationService : uses +ProjectApplicationService --> ProjectRepository : uses +ProjectApplicationService --> GitIntegrationAdapter : uses +ProjectApplicationService ..> Project : manages +ProjectRepository ..> Project : persists + +@enduml \ No newline at end of file diff --git a/docs/Project/UC10-DLSD.puml b/docs/Project/UC10-DLSD.puml new file mode 100644 index 0000000..38a691e --- /dev/null +++ b/docs/Project/UC10-DLSD.puml @@ -0,0 +1,43 @@ + @startuml +title UC-01: Create Project - Design-Level Sequence Diagram (DLSD) + +actor "Project Owner" as Owner +participant "Kleff Dashboard (SPA)" as SPA +participant "Project API Controller" as Controller +participant "Project Application Service" as AppService +participant "Project Aggregate" as Domain +participant "ProjectRepository" as Repo +participant "GitIntegrationAdapter" as GitAdapter +database "project-db" as DB + +Owner -> SPA: Clicks "Create Project" +SPA -> Controller: POST /api/projects (name, repoUrl) +activate Controller + +Controller -> AppService: createProject(projectData) +activate AppService + +AppService -> Domain: validate(projectData) +activate Domain +Domain --> AppService: valid +deactivate Domain + +AppService -> GitAdapter: configureWebhooks(repoUrl) +activate GitAdapter +GitAdapter --> AppService: webhookConfigId +deactivate GitAdapter + +AppService -> Repo: save(new Project()) +activate Repo +Repo -> DB: INSERT INTO projects ... +DB --> Repo: Success +Repo --> AppService: projectEntity +deactivate Repo + +AppService --> Controller: projectResponseDTO +deactivate AppService + +Controller --> SPA: 201 Created (Project JSON) +deactivate Controller +SPA --> Owner: Displays Project Dashboard +@enduml \ No newline at end of file diff --git a/docs/Project/UC10-SSD.puml b/docs/Project/UC10-SSD.puml new file mode 100644 index 0000000..750d579 --- /dev/null +++ b/docs/Project/UC10-SSD.puml @@ -0,0 +1,16 @@ +@startuml +title UC-01: Create Project - System Sequence Diagram (SSD) +skinparam ParticipantPadding 20 +skinparam BoxPadding 10 + +actor "Project Owner" as Owner +participant "Kleff Hosting Platform" as System + +Owner -> System: createProject(name, description, settings) +activate System + System -> System: Validate uniqueness of project id + System -> System: Link repository & setup webhooks + System --> Owner: Project Created Confirmation (Project ID) +deactivate System + +@enduml \ No newline at end of file diff --git a/docs/Project/UC10-STD.puml b/docs/Project/UC10-STD.puml new file mode 100644 index 0000000..95ec715 --- /dev/null +++ b/docs/Project/UC10-STD.puml @@ -0,0 +1,20 @@ +@startuml +title UC-01: Create Project - State Transition Diagram (STD) + +[*] --> Creating : User Submits Form +state Creating { + [*] --> ValidatingName + ValidatingName --> ConfiguringGit : Name Unique + ConfiguringGit --> Persisting : Webhook Configured +} + +Creating --> Active : Persistence Successful +Creating --> Failed : Error (Name Conflict / Git Error) + +Active --> Archived : Project Owner Deletes/Archives +Active --> Active : Settings Updated + +Failed --> Creating : User Retries +Archived --> [*] + +@enduml \ No newline at end of file