Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements Epic 2: company workspaces and team management, including backend domain, persistence, and REST APIs plus corresponding frontend flows for creating and managing company workspaces and invitations. It also introduces basic CI scripts, husky hooks, and updates sprint status documentation.
Changes:
- Add company, membership, and invitation domain models, repositories, use cases, REST resources, and database migrations to support workspace creation, dashboards, member management, and invitations.
- Implement frontend company context, pages (company dashboard, workspace creation, team settings, accept invitation), and UI components (select, toast wiring, layout tweaks) wired to the new backend APIs.
- Introduce husky pre-commit/pre-push hooks, CI helper scripts, and mark Epic 2 stories as done in sprint documentation.
Reviewed changes
Copilot reviewed 84 out of 140 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds prepare script for husky and CI helper scripts for web and API builds/tests. |
| package-lock.json | Locks new dependencies (husky, @radix-ui/react-select and related packages). |
| docs/implementation-artifacts/sprint-status.yaml | Marks Epic 2 and all its stories as done. |
| apps/web/vite.config.js | Minor import formatting; no functional change. |
| apps/web/tailwind.config.ts | Minor import formatting; no functional change. |
| apps/web/src/pages/TeamSettingsPage.tsx | New Team Settings page for listing members, inviting users, and changing roles via the company APIs. |
| apps/web/src/pages/CreateCompanyPage.tsx | New page hosting CreateCompanyForm inside the onboarding layout for workspace creation. |
| apps/web/src/pages/CompanyDashboardPage.tsx | New company dashboard shell showing member count and onboarding “Get Started” steps; redirects users without a company to the create-company flow using hasFetchedCompanies. |
| apps/web/src/pages/AcceptInvitationPage.tsx | Public page to view invitation details and accept/decline; contains a bug where the login redirect uses a redirect query param that the current login flow ignores. |
| apps/web/src/lib/utils.ts | Minor import formatting; no functional change. |
| apps/web/src/hooks/use-toast.ts | Minor import formatting; continues to expose toast hook used by new pages. |
| apps/web/src/features/company/index.ts | Barrel file exporting CompanyProvider, useCompany, CreateCompanyForm, and company API functions. |
| apps/web/src/features/company/api.ts | Frontend API client/types for companies, dashboards, members, invitations, and roles; aligns shapes with new backend responses. |
| apps/web/src/features/company/CreateCompanyForm.tsx | Form for creating a company workspace with slug generation, server-side field error handling, and navigation to /dashboard on success. |
| apps/web/src/features/company/CompanyContext.tsx | React context for company state (companies list, current company, dashboard, loading/error states, hasFetchedCompanies flag) and methods to refresh or create companies. |
| apps/web/src/features/auth/LoginForm.tsx | Reorders React imports; login behavior (including post-login redirect via location.state.from) unchanged. |
| apps/web/src/features/auth/AuthContext.tsx | Improves token-refresh path by fetching fresh user data after refresh and updating localStorage. |
| apps/web/src/components/ui/toaster.tsx | Simplifies imports; continues to render toasts based on useToast. |
| apps/web/src/components/ui/toast.tsx | Import formatting only; toast behavior unchanged. |
| apps/web/src/components/ui/separator.tsx | Import formatting only. |
| apps/web/src/components/ui/select.tsx | New Radix-based Select UI component with trigger, content, items, and scroll buttons; used in role selection in Team Settings. |
| apps/web/src/components/ui/label.tsx | Import formatting only. |
| apps/web/src/components/ui/input.tsx | Import formatting only. |
| apps/web/src/components/ui/input.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/index.ts | Re-exports useToast and the new Select components through the UI barrel file. |
| apps/web/src/components/ui/form-input.tsx | Import formatting only. |
| apps/web/src/components/ui/dropdown-menu.tsx | Import formatting only. |
| apps/web/src/components/ui/dropdown-menu.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/dialog.tsx | Import formatting only. |
| apps/web/src/components/ui/dialog.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/card.tsx | Import formatting only. |
| apps/web/src/components/ui/card.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/button.tsx | Import formatting only. |
| apps/web/src/components/ui/button.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/badge.tsx | Import formatting only. |
| apps/web/src/components/ui/badge.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/avatar.tsx | Import formatting only. |
| apps/web/src/components/ui/avatar.stories.tsx | Import formatting only. |
| apps/web/src/components/ui/alert.tsx | Import formatting only. |
| apps/web/src/components/ui/alert.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/WorkspaceSwitcher.tsx | Import formatting only; workspace switcher remains wired to company list. |
| apps/web/src/components/layout/WorkspaceSwitcher.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/UserMenu.tsx | Import formatting and hook path update; user menu logic unchanged. |
| apps/web/src/components/layout/TabNav.tsx | Import formatting only. |
| apps/web/src/components/layout/TabNav.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/PublicPageLayout.tsx | Import formatting only. |
| apps/web/src/components/layout/PublicHeader.tsx | Import formatting only. |
| apps/web/src/components/layout/ProgressStepper.tsx | Import formatting only. |
| apps/web/src/components/layout/ProgressStepper.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/PageLoading.tsx | Import formatting only. |
| apps/web/src/components/layout/PageLoading.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/PageError.tsx | Import formatting only. |
| apps/web/src/components/layout/PageError.stories.tsx | Import formatting only. |
| apps/web/src/components/layout/OnboardingLayout.tsx | Import formatting only; used by the new create-company page. |
| apps/web/src/components/layout/Navbar.tsx | Import formatting only. |
| apps/web/src/components/layout/Logo.tsx | Import formatting only. |
| apps/web/src/components/layout/Footer.tsx | Import formatting only. |
| apps/web/src/components/layout/DashboardLayout.tsx | Import formatting; continues to host navbar, optional tabs, and main content for dashboard-like pages. |
| apps/web/src/components/layout/AdminLayout.tsx | Import formatting only. |
| apps/web/src/components/common/LoadingSpinner.tsx | Import formatting only. |
| apps/web/src/components/common/LoadingSpinner.stories.tsx | Import formatting only. |
| apps/web/src/components/common/ErrorBoundary.tsx | Import formatting only. |
| apps/web/src/App.tsx | Wires new routes for company creation, company dashboard, team settings, and invitation acceptance; wraps routes in AuthProvider and CompanyProvider. |
| apps/web/playwright.config.ts | Import formatting only. |
| apps/web/package.json | Adds @radix-ui/react-select dependency for the new select component. |
| apps/web/e2e/pages/index.ts | Import formatting only. |
| apps/web/e2e/fixtures/index.ts | Import formatting only. |
| apps/web/e2e/app.spec.ts | Import formatting only. |
| apps/web/.storybook/preview.ts | Import formatting only. |
| apps/web/.storybook/main.ts | Import formatting only. |
| apps/api/src/test/java/com/upkeep/application/usecase/CreateCompanyUseCaseImplTest.java | New unit tests for CreateCompanyUseCaseImpl covering success, slug generation, slug conflict, name length, and owner role assignment. |
| apps/api/src/main/resources/db/migration/V5__create_companies_and_memberships_tables.sql | Adds companies and memberships tables plus indexes and uniqueness constraints to support workspaces and memberships. |
| apps/api/src/main/resources/db/migration/V6__create_invitations_table.sql | Adds invitations table and indexes for tokens, email/status, and company/email/status. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/membership/MembershipMapper.java | Maps between Membership domain model and MembershipEntity. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/membership/MembershipJpaRepository.java | Panache-based implementation of MembershipRepository for saving, querying, and deleting memberships. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/membership/MembershipEntity.java | JPA entity mapping for the memberships table. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/invitation/InvitationMapper.java | Maps between Invitation domain model and InvitationEntity. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/invitation/InvitationJpaRepository.java | Panache-based implementation of InvitationRepository. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/invitation/InvitationEntity.java | JPA entity mapping for the invitations table. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/company/CompanyMapper.java | Maps between Company domain model and CompanyEntity. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/company/CompanyJpaRepository.java | Panache-based implementation of CompanyRepository. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/persistence/company/CompanyEntity.java | JPA entity mapping for the companies table. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/out/email/MockEmailService.java | Extends mock email service to log invitation emails with acceptance links. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/invitation/InvitationResource.java | REST resource for GET/accept invitation endpoints using the new invitation use cases and cookie-based auth for acceptance. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/invitation/InvitationDetailsResponse.java | DTO for invitation details returned by the GET invitation endpoint. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/invitation/AcceptInvitationResponse.java | DTO for the result of accepting an invitation. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/UpdateMemberRoleRequest.java | Request DTO with validation for role updates in the member management endpoint. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/MemberResponse.java | DTO for individual member records in the company members list. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/InviteUserRequest.java | Request DTO with email/role validation for inviting a user to a company. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/InvitationResponse.java | DTO for invitations created via the company invitation endpoint. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/CreateCompanyRequest.java | Request DTO for creating companies with bean validation mirroring domain constraints. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/CompanyResponse.java | DTO for create-company responses, including the creator’s membership. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/CompanyResource.java | REST resource exposing company CRUD-like operations, dashboard, invitations, members list, and role updates, all using cookie-based auth. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/CompanyListResponse.java | DTO for listing companies associated with the current user. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/company/CompanyDashboardResponse.java | DTO for the company dashboard, including user role and basic stats. |
| apps/api/src/main/java/com/upkeep/infrastructure/adapter/in/rest/common/exception/GlobalExceptionMapper.java | Extends domain exception mapping to cover company, membership, invitation, and authorization-related errors with specific codes and HTTP statuses. |
| apps/api/src/main/java/com/upkeep/domain/model/membership/Role.java | Defines membership roles (OWNER, MEMBER) shared across use cases and APIs. |
| apps/api/src/main/java/com/upkeep/domain/model/membership/MembershipId.java | Strongly-typed ID wrapper for memberships. |
| apps/api/src/main/java/com/upkeep/domain/model/membership/Membership.java | Domain model for company membership, including role changes and timestamps. |
| apps/api/src/main/java/com/upkeep/domain/model/invitation/InvitationToken.java | Value object for invitation tokens; generates secure random, URL-safe tokens. |
| apps/api/src/main/java/com/upkeep/domain/model/invitation/InvitationStatus.java | Domain enum for invitation states (PENDING, ACCEPTED, DECLINED, EXPIRED). |
| apps/api/src/main/java/com/upkeep/domain/model/invitation/InvitationId.java | Strongly-typed ID wrapper for invitations. |
| apps/api/src/main/java/com/upkeep/domain/model/invitation/Invitation.java | Domain model for invitations, including creation, expiration, acceptance, and status transitions. |
| apps/api/src/main/java/com/upkeep/domain/model/company/CompanySlug.java | Value object for company slugs; enforces format and length, but currently throws IllegalArgumentException causing 500s for some invalid inputs. |
| apps/api/src/main/java/com/upkeep/domain/model/company/CompanyName.java | Value object for company names; enforces length but uses IllegalArgumentException instead of domain validation. |
| apps/api/src/main/java/com/upkeep/domain/model/company/CompanyId.java | Strongly-typed ID wrapper for companies. |
| apps/api/src/main/java/com/upkeep/domain/model/company/Company.java | Domain model for a company, with ID, name, slug, and timestamps. |
| apps/api/src/main/java/com/upkeep/domain/exception/UnauthorizedOperationException.java | Domain exception for forbidden operations (e.g., non-owners changing roles or inviting). |
| apps/api/src/main/java/com/upkeep/domain/exception/MembershipNotFoundException.java | Domain exception representing a missing membership for a given customer and company. |
| apps/api/src/main/java/com/upkeep/domain/exception/LastOwnerException.java | Domain exception thrown when attempting to remove the last owner from a company. |
| apps/api/src/main/java/com/upkeep/domain/exception/InvitationNotFoundException.java | Domain exception for unknown invitation tokens. |
| apps/api/src/main/java/com/upkeep/domain/exception/InvitationExpiredException.java | Domain exception for expired invitations. |
| apps/api/src/main/java/com/upkeep/domain/exception/InvitationAlreadyExistsException.java | Domain exception for conflicting pending invitations for the same email. |
| apps/api/src/main/java/com/upkeep/domain/exception/CompanySlugAlreadyExistsException.java | Domain exception for slug uniqueness violations. |
| apps/api/src/main/java/com/upkeep/domain/exception/CompanyNotFoundException.java | Domain exception for missing companies. |
| apps/api/src/main/java/com/upkeep/domain/exception/AlreadyMemberException.java | Domain exception raised when accepting an invitation but already a member. |
| apps/api/src/main/java/com/upkeep/application/usecase/UpdateMemberRoleUseCaseImpl.java | Use case to change member roles with ownership checks and last-owner protection; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/InviteUserToCompanyUseCaseImpl.java | Use case to invite users to a company with ownership and duplicate-invitation checks; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/GetUserCompaniesUseCaseImpl.java | Use case to list companies for a user via memberships; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/GetInvitationUseCaseImpl.java | Use case to load invitation details (including company name/status); currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/GetCompanyMembersUseCaseImpl.java | Use case to list members of a company after verifying caller membership; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/GetCompanyDashboardUseCaseImpl.java | Use case to build a basic company dashboard view and stats; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/CreateCompanyUseCaseImpl.java | Use case to create a company and assign the creator as owner; covered by the new tests. |
| apps/api/src/main/java/com/upkeep/application/usecase/AcceptInvitationUseCaseImpl.java | Use case to accept invitations, handle expiration, and create memberships; currently lacks unit tests. |
| apps/api/src/main/java/com/upkeep/application/port/out/notification/EmailService.java | Port extended with sendInvitationEmail for invitation notifications. |
| apps/api/src/main/java/com/upkeep/application/port/out/membership/MembershipRepository.java | Port definition for membership persistence operations. |
| apps/api/src/main/java/com/upkeep/application/port/out/invitation/InvitationRepository.java | Port definition for invitation persistence operations. |
| apps/api/src/main/java/com/upkeep/application/port/out/company/CompanyRepository.java | Port definition for company persistence operations. |
| apps/api/src/main/java/com/upkeep/application/port/in/UpdateMemberRoleUseCase.java | Port for the update-member-role use case with command/result records. |
| apps/api/src/main/java/com/upkeep/application/port/in/InviteUserToCompanyUseCase.java | Port for the invite-user use case with command/result records. |
| apps/api/src/main/java/com/upkeep/application/port/in/GetUserCompaniesUseCase.java | Port for fetching companies for a user. |
| apps/api/src/main/java/com/upkeep/application/port/in/GetInvitationUseCase.java | Port for fetching invitation details by token. |
| apps/api/src/main/java/com/upkeep/application/port/in/GetCompanyMembersUseCase.java | Port for fetching members of a company. |
| apps/api/src/main/java/com/upkeep/application/port/in/GetCompanyDashboardUseCase.java | Port for fetching a company dashboard view. |
| apps/api/src/main/java/com/upkeep/application/port/in/CreateCompanyUseCase.java | Port for the create-company use case. |
| apps/api/src/main/java/com/upkeep/application/port/in/AcceptInvitationUseCase.java | Port for accepting an invitation. |
| Copilot-Processing.md | Internal notes summarizing analysis and implementation steps for Epic 2; no runtime impact. |
| .husky/pre-push | New Git hook to run web lint/build and API checkstyle/tests/build before allowing pushes. |
| .husky/pre-commit | New Git hook to run a quick web lint before committing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
apps/api/src/main/java/com/upkeep/application/usecase/AcceptInvitationUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/InviteUserToCompanyUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/GetCompanyDashboardUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/GetUserCompaniesUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/GetInvitationUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/UpdateMemberRoleUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/application/usecase/GetCompanyMembersUseCaseImpl.java
Show resolved
Hide resolved
apps/api/src/main/java/com/upkeep/domain/model/company/CompanySlug.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 101 out of 157 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Path("/api/companies") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| @Consumes(MediaType.APPLICATION_JSON) | ||
| public class CompanyResource { | ||
|
|
||
| private static final String ACCESS_TOKEN_COOKIE = "access_token"; | ||
|
|
||
| private final CreateCompanyUseCase createCompanyUseCase; | ||
| private final GetUserCompaniesUseCase getUserCompaniesUseCase; | ||
| private final GetCompanyDashboardUseCase getCompanyDashboardUseCase; | ||
| private final InviteUserToCompanyUseCase inviteUserToCompanyUseCase; | ||
| private final GetCompanyMembersUseCase getCompanyMembersUseCase; | ||
| private final UpdateMemberRoleUseCase updateMemberRoleUseCase; | ||
| private final TokenService tokenService; | ||
|
|
||
| public CompanyResource(CreateCompanyUseCase createCompanyUseCase, | ||
| GetUserCompaniesUseCase getUserCompaniesUseCase, | ||
| GetCompanyDashboardUseCase getCompanyDashboardUseCase, | ||
| InviteUserToCompanyUseCase inviteUserToCompanyUseCase, | ||
| GetCompanyMembersUseCase getCompanyMembersUseCase, | ||
| UpdateMemberRoleUseCase updateMemberRoleUseCase, | ||
| TokenService tokenService) { | ||
| this.createCompanyUseCase = createCompanyUseCase; | ||
| this.getUserCompaniesUseCase = getUserCompaniesUseCase; | ||
| this.getCompanyDashboardUseCase = getCompanyDashboardUseCase; | ||
| this.inviteUserToCompanyUseCase = inviteUserToCompanyUseCase; | ||
| this.getCompanyMembersUseCase = getCompanyMembersUseCase; | ||
| this.updateMemberRoleUseCase = updateMemberRoleUseCase; | ||
| this.tokenService = tokenService; | ||
| } | ||
|
|
||
| @POST | ||
| public Response createCompany(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken, | ||
| @Valid CreateCompanyRequest request) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| CreateCompanyResult result = createCompanyUseCase.execute( | ||
| new CreateCompanyCommand( | ||
| claims.userId(), | ||
| request.name(), | ||
| request.slug() | ||
| ) | ||
| ); | ||
|
|
||
| CompanyResponse response = new CompanyResponse( | ||
| result.companyId(), | ||
| result.name(), | ||
| result.slug(), | ||
| new CompanyResponse.MembershipResponse( | ||
| result.membership().membershipId(), | ||
| result.membership().role() | ||
| ) | ||
| ); | ||
|
|
||
| return Response.status(201) | ||
| .entity(ApiResponse.success(response)) | ||
| .build(); | ||
| } | ||
|
|
||
| @GET | ||
| public Response getUserCompanies(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| List<CompanyWithMembership> companies = getUserCompaniesUseCase.execute( | ||
| new GetUserCompaniesQuery(claims.userId()) | ||
| ); | ||
|
|
||
| List<CompanyListResponse> response = companies.stream() | ||
| .map(c -> new CompanyListResponse(c.companyId(), c.name(), c.slug(), c.role())) | ||
| .toList(); | ||
|
|
||
| return Response.ok(ApiResponse.success(response)).build(); | ||
| } | ||
|
|
||
| @GET | ||
| @Path("/{companyId}/dashboard") | ||
| public Response getCompanyDashboard(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken, | ||
| @PathParam("companyId") String companyId) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| CompanyDashboard dashboard = getCompanyDashboardUseCase.execute( | ||
| new GetCompanyDashboardQuery(claims.userId(), companyId) | ||
| ); | ||
|
|
||
| CompanyDashboardResponse response = new CompanyDashboardResponse( | ||
| dashboard.companyId(), | ||
| dashboard.name(), | ||
| dashboard.slug(), | ||
| dashboard.userRole(), | ||
| new CompanyDashboardResponse.StatsResponse( | ||
| dashboard.stats().totalMembers(), | ||
| dashboard.stats().hasBudget(), | ||
| dashboard.stats().hasPackages(), | ||
| dashboard.stats().hasAllocations() | ||
| ) | ||
| ); | ||
|
|
||
| return Response.ok(ApiResponse.success(response)).build(); | ||
| } | ||
|
|
||
| @POST | ||
| @Path("/{companyId}/invitations") | ||
| public Response inviteUser(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken, | ||
| @PathParam("companyId") String companyId, | ||
| @Valid InviteUserRequest request) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| InviteResult result = inviteUserToCompanyUseCase.execute( | ||
| new InviteCommand( | ||
| claims.userId(), | ||
| companyId, | ||
| request.email(), | ||
| request.role() | ||
| ) | ||
| ); | ||
|
|
||
| InvitationResponse response = new InvitationResponse( | ||
| result.invitationId(), | ||
| result.email(), | ||
| result.role(), | ||
| result.status(), | ||
| result.expiresAt() | ||
| ); | ||
|
|
||
| return Response.status(201) | ||
| .entity(ApiResponse.success(response)) | ||
| .build(); | ||
| } | ||
|
|
||
| @GET | ||
| @Path("/{companyId}/members") | ||
| public Response getCompanyMembers(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken, | ||
| @PathParam("companyId") String companyId) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| List<MemberInfo> members = getCompanyMembersUseCase.execute( | ||
| new GetCompanyMembersQuery(claims.userId(), companyId) | ||
| ); | ||
|
|
||
| List<MemberResponse> response = members.stream() | ||
| .map(m -> new MemberResponse( | ||
| m.membershipId(), | ||
| m.customerId(), | ||
| m.email(), | ||
| m.role(), | ||
| m.joinedAt() | ||
| )) | ||
| .toList(); | ||
|
|
||
| return Response.ok(ApiResponse.success(response)).build(); | ||
| } | ||
|
|
||
| @PATCH | ||
| @Path("/{companyId}/members/{membershipId}") | ||
| public Response updateMemberRole(@CookieParam(ACCESS_TOKEN_COOKIE) String accessToken, | ||
| @PathParam("companyId") String companyId, | ||
| @PathParam("membershipId") String membershipId, | ||
| @Valid UpdateMemberRoleRequest request) { | ||
| TokenClaims claims = validateToken(accessToken); | ||
| if (claims == null) { | ||
| return unauthorizedResponse(); | ||
| } | ||
|
|
||
| UpdateMemberRoleResult result = updateMemberRoleUseCase.execute( | ||
| new UpdateMemberRoleCommand( | ||
| claims.userId(), | ||
| companyId, | ||
| membershipId, | ||
| request.role() | ||
| ) | ||
| ); | ||
|
|
||
| return Response.ok(ApiResponse.success(result)).build(); | ||
| } | ||
|
|
||
| private TokenClaims validateToken(String accessToken) { | ||
| if (accessToken == null || accessToken.isBlank()) { | ||
| return null; | ||
| } | ||
| try { | ||
| return tokenService.validateAccessToken(accessToken); | ||
| } catch (Exception e) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| private Response unauthorizedResponse() { | ||
| return Response.status(401) | ||
| .entity(ApiResponse.error(new ApiError( | ||
| "UNAUTHORIZED", "Authentication required", null, null))) | ||
| .build(); | ||
| } | ||
| } |
There was a problem hiding this comment.
The new company REST endpoints expose multiple behaviors (creating companies, listing user companies, dashboard stats, inviting members, listing members, updating roles) but there are no corresponding integration tests for this resource, while other REST resources (e.g. AuthResource, see apps/api/src/test/java/com/upkeep/infrastructure/adapter/in/rest/auth/AuthResourceTest.java) are covered. To keep API behavior stable and prevent regressions, please add Quarkus REST tests for these endpoints (happy paths and key error cases like unauthorized, forbidden, and domain exceptions).
No description provided.