From a1de374d22beedce803b685a3266e45c0d8cbe57 Mon Sep 17 00:00:00 2001 From: James Wampler Date: Tue, 30 Jun 2026 13:36:18 -0700 Subject: [PATCH] Move org switcher to header, self-host Inter, add feature tags, normalize API route casing - Replace sidebar org dropdown with a header link/menu (left of theme toggle) that includes a "New Organization" entry opening the create dialog - Self-host Inter via @fontsource-variable/inter instead of relying on system fonts - Wire up real feature<->tag assignment (was UI-only before): expose Tags on FeatureResponse, add assign/remove endpoints, make tag chips in the feature detail dialog toggle assignment, and show up to 5 tags (+ellipsis) in the features table next to the segment-override icon - Normalize all API routes to singular/plural REST convention: singular resource name when addressing one item by id/key (e.g. /feature/{id}), plural for list/create endpoints (e.g. /features). Updated every controller, the admin frontend API clients, and affected tests to match --- src/admin/package-lock.json | 9 + src/admin/package.json | 1 + .../template/libs/vuetify/_variables.scss | 2 +- src/admin/src/api/apiKeys.ts | 6 +- src/admin/src/api/auditLogs.ts | 4 +- src/admin/src/api/environments.ts | 8 +- src/admin/src/api/featureSegments.ts | 10 +- src/admin/src/api/featureStates.ts | 8 +- src/admin/src/api/features.ts | 34 +++- src/admin/src/api/identities.ts | 18 +- src/admin/src/api/organizations.ts | 20 +-- src/admin/src/api/projects.ts | 14 +- src/admin/src/api/segments.ts | 10 +- src/admin/src/api/tags.ts | 6 +- src/admin/src/api/usage.ts | 2 +- src/admin/src/api/users.ts | 8 +- src/admin/src/api/webhooks.ts | 18 +- .../src/components/features/FeatureDetail.vue | 6 +- .../src/components/features/TagsChipInput.vue | 44 ++++- .../src/components/nav/OrgHeaderSwitcher.vue | 164 ++++++++++++++++++ .../DefaultLayoutWithVerticalNav.vue | 2 + src/admin/src/layouts/components/NavItems.vue | 2 - src/admin/src/main.ts | 1 + src/admin/src/types/api.ts | 1 + src/admin/src/views/FeaturesView.vue | 17 +- .../MicCheck.Api/Audit/AuditLogsController.cs | 6 +- .../Security/ApiKeys/ApiKeyEndpoints.cs | 8 +- .../Environments/EnvironmentsController.cs | 61 ++++--- .../MicCheck.Api/Features/FeatureResponse.cs | 6 +- .../MicCheck.Api/Features/FeatureService.cs | 39 ++++- .../Features/FeatureUsageController.cs | 2 +- .../Features/FeaturesController.cs | 44 ++++- .../MicCheck.Api/Features/TagsController.cs | 7 +- .../Identities/IdentitiesController.cs | 2 +- .../Organizations/OrganizationsController.cs | 35 ++-- .../Projects/ProjectsController.cs | 19 +- .../Segments/SegmentsController.cs | 11 +- src/api/MicCheck.Api/Users/UsersController.cs | 2 +- .../Admin/AdminApiIntegrationTests.cs | 8 +- .../ApiKeyAuthenticationHandlerTests.cs | 12 +- .../ApiKeyAuthenticationHandlerTests.cs | 12 +- .../Features/FeatureServiceTests.cs | 59 +++++++ .../Features/FlagsApiIntegrationTests.cs | 2 +- 43 files changed, 564 insertions(+), 186 deletions(-) create mode 100644 src/admin/src/components/nav/OrgHeaderSwitcher.vue diff --git a/src/admin/package-lock.json b/src/admin/package-lock.json index 696cf9e..63d9467 100755 --- a/src/admin/package-lock.json +++ b/src/admin/package-lock.json @@ -8,6 +8,7 @@ "name": "mic-check-admin", "version": "1.0.0", "dependencies": { + "@fontsource-variable/inter": "^5.2.8", "@iconify-json/bxl": "^1.2.0", "@iconify-json/ri": "^1.2.0", "@iconify/vue": "^4.1.0", @@ -2002,6 +2003,14 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", diff --git a/src/admin/package.json b/src/admin/package.json index 72e0458..3e016ce 100755 --- a/src/admin/package.json +++ b/src/admin/package.json @@ -10,6 +10,7 @@ "test": "jest --passWithNoTests" }, "dependencies": { + "@fontsource-variable/inter": "^5.2.8", "@iconify-json/bxl": "^1.2.0", "@iconify-json/ri": "^1.2.0", "@iconify/vue": "^4.1.0", diff --git a/src/admin/src/@core/scss/template/libs/vuetify/_variables.scss b/src/admin/src/@core/scss/template/libs/vuetify/_variables.scss index ff857aa..70bc70f 100755 --- a/src/admin/src/@core/scss/template/libs/vuetify/_variables.scss +++ b/src/admin/src/@core/scss/template/libs/vuetify/_variables.scss @@ -1,4 +1,4 @@ -$font-family-custom: "Inter", sans-serif, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, +$font-family-custom: "Inter Variable", sans-serif, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; /* stylelint-disable length-zero-no-unit */ @forward "../../../base/libs/vuetify/variables" with ( diff --git a/src/admin/src/api/apiKeys.ts b/src/admin/src/api/apiKeys.ts index 75aa472..0032d65 100755 --- a/src/admin/src/api/apiKeys.ts +++ b/src/admin/src/api/apiKeys.ts @@ -3,7 +3,7 @@ import type { ApiKeyResponse, CreateApiKeyRequest, CreateApiKeyResponse } from ' export async function listApiKeys(organizationId: number): Promise { const { data } = await apiClient.get( - `/v1/organisations/${organizationId}/api-keys`, + `/v1/organisation/${organizationId}/api-keys`, ); return data; } @@ -13,12 +13,12 @@ export async function createApiKey( request: CreateApiKeyRequest, ): Promise { const { data } = await apiClient.post( - `/v1/organisations/${organizationId}/api-keys`, + `/v1/organisation/${organizationId}/api-keys`, request, ); return data; } export async function deleteApiKey(organizationId: number, keyId: number): Promise { - await apiClient.delete(`/v1/organisations/${organizationId}/api-keys/${keyId}`); + await apiClient.delete(`/v1/organisation/${organizationId}/api-key/${keyId}`); } diff --git a/src/admin/src/api/auditLogs.ts b/src/admin/src/api/auditLogs.ts index 5ecb1e6..e2c94bc 100755 --- a/src/admin/src/api/auditLogs.ts +++ b/src/admin/src/api/auditLogs.ts @@ -17,7 +17,7 @@ export async function listAuditLogsByOrganization( filter: AuditLogFilter = {}, ): Promise> { const { data } = await apiClient.get>( - `/v1/organisations/${orgId}/audit-logs`, + `/v1/organisation/${orgId}/audit-logs`, { params: toParams(filter) }, ); return data; @@ -28,7 +28,7 @@ export async function listAuditLogsByProject( filter: AuditLogFilter = {}, ): Promise> { const { data } = await apiClient.get>( - `/v1/projects/${projectId}/audit-logs`, + `/v1/project/${projectId}/audit-logs`, { params: toParams(filter) }, ); return data; diff --git a/src/admin/src/api/environments.ts b/src/admin/src/api/environments.ts index 0238e61..952810b 100755 --- a/src/admin/src/api/environments.ts +++ b/src/admin/src/api/environments.ts @@ -16,7 +16,7 @@ export async function listEnvironments(projectId: number, page = 1, pageSize = 1 } export async function getEnvironment(apiKey: string): Promise { - const { data } = await apiClient.get(`/v1/environments/${apiKey}`); + const { data } = await apiClient.get(`/v1/environment/${apiKey}`); return data; } @@ -26,17 +26,17 @@ export async function createEnvironment(request: CreateEnvironmentRequest): Prom } export async function updateEnvironment(apiKey: string, request: UpdateEnvironmentRequest): Promise { - const { data } = await apiClient.put(`/v1/environments/${apiKey}`, request); + const { data } = await apiClient.put(`/v1/environment/${apiKey}`, request); return data; } export async function deleteEnvironment(apiKey: string): Promise { - await apiClient.delete(`/v1/environments/${apiKey}`); + await apiClient.delete(`/v1/environment/${apiKey}`); } export async function cloneEnvironment(apiKey: string, request: CloneEnvironmentRequest): Promise { const { data } = await apiClient.post( - `/v1/environments/${apiKey}/clone`, + `/v1/environment/${apiKey}/clone`, request, ); return data; diff --git a/src/admin/src/api/featureSegments.ts b/src/admin/src/api/featureSegments.ts index 3847b67..b68ca75 100755 --- a/src/admin/src/api/featureSegments.ts +++ b/src/admin/src/api/featureSegments.ts @@ -11,7 +11,7 @@ export async function listFeatureSegments( featureId: number, ): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/features/${featureId}/segments`, + `/v1/environment/${envApiKey}/feature/${featureId}/segments`, ); return data; } @@ -22,7 +22,7 @@ export async function createFeatureSegment( request: CreateFeatureSegmentRequest, ): Promise { const { data } = await apiClient.post( - `/v1/environments/${envApiKey}/features/${featureId}/segments`, + `/v1/environment/${envApiKey}/feature/${featureId}/segments`, request, ); return data; @@ -35,7 +35,7 @@ export async function updateFeatureSegment( request: UpdateFeatureSegmentRequest, ): Promise { const { data } = await apiClient.put( - `/v1/environments/${envApiKey}/features/${featureId}/segments/${id}`, + `/v1/environment/${envApiKey}/feature/${featureId}/segment/${id}`, request, ); return data; @@ -46,7 +46,7 @@ export async function deleteFeatureSegment( featureId: number, id: number, ): Promise { - await apiClient.delete(`/v1/environments/${envApiKey}/features/${featureId}/segments/${id}`); + await apiClient.delete(`/v1/environment/${envApiKey}/feature/${featureId}/segment/${id}`); } export async function listIdentitySegments( @@ -54,7 +54,7 @@ export async function listIdentitySegments( identityId: number, ): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/identities/${identityId}/segments`, + `/v1/environment/${envApiKey}/identity/${identityId}/segments`, ); return data; } diff --git a/src/admin/src/api/featureStates.ts b/src/admin/src/api/featureStates.ts index 2c8e122..8451d28 100755 --- a/src/admin/src/api/featureStates.ts +++ b/src/admin/src/api/featureStates.ts @@ -7,14 +7,14 @@ import type { export async function listFeatureStates(envApiKey: string): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/featurestates`, + `/v1/environment/${envApiKey}/featurestates`, ); return data; } export async function getFeatureState(envApiKey: string, id: number): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/featurestates/${id}`, + `/v1/environment/${envApiKey}/featurestate/${id}`, ); return data; } @@ -25,7 +25,7 @@ export async function updateFeatureState( request: UpdateFeatureStateRequest, ): Promise { const { data } = await apiClient.put( - `/v1/environments/${envApiKey}/featurestates/${id}`, + `/v1/environment/${envApiKey}/featurestate/${id}`, request, ); return data; @@ -37,7 +37,7 @@ export async function patchFeatureState( request: PatchFeatureStateRequest, ): Promise { const { data } = await apiClient.patch( - `/v1/environments/${envApiKey}/featurestates/${id}`, + `/v1/environment/${envApiKey}/featurestate/${id}`, request, ); return data; diff --git a/src/admin/src/api/features.ts b/src/admin/src/api/features.ts index d013ac7..5b1f8f6 100755 --- a/src/admin/src/api/features.ts +++ b/src/admin/src/api/features.ts @@ -13,7 +13,7 @@ export async function listFeatures( pageSize = 100, ): Promise> { const { data } = await apiClient.get>( - `/v1/projects/${projectId}/features`, + `/v1/project/${projectId}/features`, { params: { page, pageSize } }, ); return data; @@ -21,7 +21,7 @@ export async function listFeatures( export async function getFeature(projectId: number, id: number): Promise { const { data } = await apiClient.get( - `/v1/projects/${projectId}/features/${id}`, + `/v1/project/${projectId}/feature/${id}`, ); return data; } @@ -31,7 +31,7 @@ export async function createFeature( request: CreateFeatureRequest, ): Promise { const { data } = await apiClient.post( - `/v1/projects/${projectId}/features`, + `/v1/project/${projectId}/features`, request, ); return data; @@ -43,7 +43,7 @@ export async function updateFeature( request: UpdateFeatureRequest, ): Promise { const { data } = await apiClient.put( - `/v1/projects/${projectId}/features/${id}`, + `/v1/project/${projectId}/feature/${id}`, request, ); return data; @@ -55,12 +55,34 @@ export async function patchFeature( request: PatchFeatureRequest, ): Promise { const { data } = await apiClient.patch( - `/v1/projects/${projectId}/features/${id}`, + `/v1/project/${projectId}/feature/${id}`, request, ); return data; } export async function deleteFeature(projectId: number, id: number): Promise { - await apiClient.delete(`/v1/projects/${projectId}/features/${id}`); + await apiClient.delete(`/v1/project/${projectId}/feature/${id}`); +} + +export async function assignFeatureTag( + projectId: number, + featureId: number, + tagId: number, +): Promise { + const { data } = await apiClient.put( + `/v1/project/${projectId}/feature/${featureId}/tag/${tagId}`, + ); + return data; +} + +export async function removeFeatureTag( + projectId: number, + featureId: number, + tagId: number, +): Promise { + const { data } = await apiClient.delete( + `/v1/project/${projectId}/feature/${featureId}/tag/${tagId}`, + ); + return data; } diff --git a/src/admin/src/api/identities.ts b/src/admin/src/api/identities.ts index 1b2a987..71230f0 100755 --- a/src/admin/src/api/identities.ts +++ b/src/admin/src/api/identities.ts @@ -15,7 +15,7 @@ export async function listIdentities( pageSize = 100, ): Promise> { const { data } = await apiClient.get>( - `/v1/environments/${envApiKey}/identities`, + `/v1/environment/${envApiKey}/identities`, { params: { page, pageSize } }, ); return data; @@ -26,7 +26,7 @@ export async function createIdentity( request: CreateIdentityRequest, ): Promise { const { data } = await apiClient.post( - `/v1/environments/${envApiKey}/identities`, + `/v1/environment/${envApiKey}/identities`, request, ); return data; @@ -34,7 +34,7 @@ export async function createIdentity( export async function getIdentity(envApiKey: string, id: number): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/identities/${id}`, + `/v1/environment/${envApiKey}/identity/${id}`, ); return data; } @@ -46,7 +46,7 @@ export async function upsertIdentityTrait( request: UpsertTraitRequest, ): Promise { const { data } = await apiClient.put( - `/v1/environments/${envApiKey}/identities/${identityId}/traits/${encodeURIComponent(key)}`, + `/v1/environment/${envApiKey}/identity/${identityId}/trait/${encodeURIComponent(key)}`, request, ); return data; @@ -58,12 +58,12 @@ export async function deleteIdentityTrait( key: string, ): Promise { await apiClient.delete( - `/v1/environments/${envApiKey}/identities/${identityId}/traits/${encodeURIComponent(key)}`, + `/v1/environment/${envApiKey}/identity/${identityId}/trait/${encodeURIComponent(key)}`, ); } export async function deleteIdentity(envApiKey: string, id: number): Promise { - await apiClient.delete(`/v1/environments/${envApiKey}/identities/${id}`); + await apiClient.delete(`/v1/environment/${envApiKey}/identity/${id}`); } export async function getIdentityFeatureStates( @@ -71,7 +71,7 @@ export async function getIdentityFeatureStates( identityId: number, ): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/identities/${identityId}/featurestates`, + `/v1/environment/${envApiKey}/identity/${identityId}/featurestates`, ); return data; } @@ -83,7 +83,7 @@ export async function setIdentityFeatureState( request: UpdateFeatureStateRequest, ): Promise { const { data } = await apiClient.put( - `/v1/environments/${envApiKey}/identities/${identityId}/featurestates/${featureId}`, + `/v1/environment/${envApiKey}/identity/${identityId}/featurestate/${featureId}`, request, ); return data; @@ -95,6 +95,6 @@ export async function deleteIdentityFeatureState( featureId: number, ): Promise { await apiClient.delete( - `/v1/environments/${envApiKey}/identities/${identityId}/featurestates/${featureId}`, + `/v1/environment/${envApiKey}/identity/${identityId}/featurestate/${featureId}`, ); } diff --git a/src/admin/src/api/organizations.ts b/src/admin/src/api/organizations.ts index 649f418..7d36d22 100755 --- a/src/admin/src/api/organizations.ts +++ b/src/admin/src/api/organizations.ts @@ -20,7 +20,7 @@ export async function listOrganizations(page = 1, pageSize = 100): Promise { - const { data } = await apiClient.get(`/v1/organisations/${id}`); + const { data } = await apiClient.get(`/v1/organisation/${id}`); return data; } @@ -30,27 +30,27 @@ export async function createOrganization(request: CreateOrganizationRequest): Pr } export async function updateOrganization(id: number, request: UpdateOrganizationRequest): Promise { - const { data } = await apiClient.put(`/v1/organisations/${id}`, request); + const { data } = await apiClient.put(`/v1/organisation/${id}`, request); return data; } export async function deleteOrganization(id: number): Promise { - await apiClient.delete(`/v1/organisations/${id}`); + await apiClient.delete(`/v1/organisation/${id}`); } export async function listOrganizationMembers(organizationId: number): Promise { const { data } = await apiClient.get( - `/v1/organisations/${organizationId}/users`, + `/v1/organisation/${organizationId}/users`, ); return data; } export async function inviteOrganizationMember(organizationId: number, request: InviteUserRequest): Promise { - await apiClient.post(`/v1/organisations/${organizationId}/users/invite`, request); + await apiClient.post(`/v1/organisation/${organizationId}/users/invite`, request); } export async function removeOrganizationMember(organizationId: number, userId: number): Promise { - await apiClient.delete(`/v1/organisations/${organizationId}/users/${userId}`); + await apiClient.delete(`/v1/organisation/${organizationId}/user/${userId}`); } export async function inviteOrganizationMembersByEmail( @@ -58,7 +58,7 @@ export async function inviteOrganizationMembersByEmail( request: InviteUsersByEmailRequest, ): Promise { const { data } = await apiClient.post( - `/v1/organisations/${organizationId}/users/invite-by-email`, + `/v1/organisation/${organizationId}/users/invite-by-email`, request, ); return data; @@ -66,14 +66,14 @@ export async function inviteOrganizationMembersByEmail( export async function getInviteLink(organizationId: number): Promise { const { data } = await apiClient.get( - `/v1/organisations/${organizationId}/invite-link`, + `/v1/organisation/${organizationId}/invite-link`, ); return data; } export async function regenerateInviteLink(organizationId: number): Promise { const { data } = await apiClient.post( - `/v1/organisations/${organizationId}/invite-link/regenerate`, + `/v1/organisation/${organizationId}/invite-link/regenerate`, ); return data; } @@ -83,5 +83,5 @@ export async function acceptInvite(token: string): Promise { } export async function setPrimaryOrganization(id: number): Promise { - await apiClient.put(`/v1/organisations/${id}/primary`); + await apiClient.put(`/v1/organisation/${id}/primary`); } diff --git a/src/admin/src/api/projects.ts b/src/admin/src/api/projects.ts index 6b0c2e4..90983d6 100755 --- a/src/admin/src/api/projects.ts +++ b/src/admin/src/api/projects.ts @@ -17,7 +17,7 @@ export async function listProjects(organizationId: number, page = 1, pageSize = } export async function getProject(id: number): Promise { - const { data } = await apiClient.get(`/v1/projects/${id}`); + const { data } = await apiClient.get(`/v1/project/${id}`); return data; } @@ -27,17 +27,17 @@ export async function createProject(request: CreateProjectRequest): Promise { - const { data } = await apiClient.put(`/v1/projects/${id}`, request); + const { data } = await apiClient.put(`/v1/project/${id}`, request); return data; } export async function deleteProject(id: number): Promise { - await apiClient.delete(`/v1/projects/${id}`); + await apiClient.delete(`/v1/project/${id}`); } export async function listProjectUserPermissions(projectId: number): Promise { const { data } = await apiClient.get( - `/v1/projects/${projectId}/user-permissions`, + `/v1/project/${projectId}/user-permissions`, ); return data; } @@ -47,7 +47,7 @@ export async function setProjectUserPermissions( request: SetUserPermissionsRequest, ): Promise { const { data } = await apiClient.post( - `/v1/projects/${projectId}/user-permissions`, + `/v1/project/${projectId}/user-permissions`, request, ); return data; @@ -59,12 +59,12 @@ export async function updateProjectUserPermissions( request: SetUserPermissionsRequest, ): Promise { const { data } = await apiClient.put( - `/v1/projects/${projectId}/user-permissions/${userId}`, + `/v1/project/${projectId}/user-permission/${userId}`, request, ); return data; } export async function removeProjectUserPermissions(projectId: number, userId: number): Promise { - await apiClient.delete(`/v1/projects/${projectId}/user-permissions/${userId}`); + await apiClient.delete(`/v1/project/${projectId}/user-permission/${userId}`); } diff --git a/src/admin/src/api/segments.ts b/src/admin/src/api/segments.ts index 32a4b05..7127077 100755 --- a/src/admin/src/api/segments.ts +++ b/src/admin/src/api/segments.ts @@ -8,7 +8,7 @@ import type { export async function listSegments(projectId: number, page = 1, pageSize = 100): Promise { const { data } = await apiClient.get>( - `/v1/projects/${projectId}/segments`, + `/v1/project/${projectId}/segments`, { params: { page, pageSize } }, ); return data.results; @@ -16,7 +16,7 @@ export async function listSegments(projectId: number, page = 1, pageSize = 100): export async function getSegment(projectId: number, id: number): Promise { const { data } = await apiClient.get( - `/v1/projects/${projectId}/segments/${id}`, + `/v1/project/${projectId}/segment/${id}`, ); return data; } @@ -26,7 +26,7 @@ export async function createSegment( request: CreateSegmentRequest, ): Promise { const { data } = await apiClient.post( - `/v1/projects/${projectId}/segments`, + `/v1/project/${projectId}/segments`, request, ); return data; @@ -38,12 +38,12 @@ export async function updateSegment( request: UpdateSegmentRequest, ): Promise { const { data } = await apiClient.put( - `/v1/projects/${projectId}/segments/${id}`, + `/v1/project/${projectId}/segment/${id}`, request, ); return data; } export async function deleteSegment(projectId: number, id: number): Promise { - await apiClient.delete(`/v1/projects/${projectId}/segments/${id}`); + await apiClient.delete(`/v1/project/${projectId}/segment/${id}`); } diff --git a/src/admin/src/api/tags.ts b/src/admin/src/api/tags.ts index ea704ae..3961c9e 100755 --- a/src/admin/src/api/tags.ts +++ b/src/admin/src/api/tags.ts @@ -2,15 +2,15 @@ import apiClient from './client'; import type { TagResponse, CreateTagRequest } from '@/types/api'; export async function listTags(projectId: number): Promise { - const { data } = await apiClient.get(`/v1/projects/${projectId}/tags`); + const { data } = await apiClient.get(`/v1/project/${projectId}/tags`); return data; } export async function createTag(projectId: number, request: CreateTagRequest): Promise { - const { data } = await apiClient.post(`/v1/projects/${projectId}/tags`, request); + const { data } = await apiClient.post(`/v1/project/${projectId}/tags`, request); return data; } export async function deleteTag(projectId: number, id: number): Promise { - await apiClient.delete(`/v1/projects/${projectId}/tags/${id}`); + await apiClient.delete(`/v1/project/${projectId}/tag/${id}`); } diff --git a/src/admin/src/api/usage.ts b/src/admin/src/api/usage.ts index 0d1fd71..73c00c1 100644 --- a/src/admin/src/api/usage.ts +++ b/src/admin/src/api/usage.ts @@ -2,6 +2,6 @@ import apiClient from './client'; import type { DashboardUsageResponse } from '@/types/api'; export async function getUsageDashboard(environmentId: number, days = 14): Promise { - const { data } = await apiClient.get(`/v1/environments/${environmentId}/usage`, { params: { days } }); + const { data } = await apiClient.get(`/v1/environment/${environmentId}/usage`, { params: { days } }); return data; } diff --git a/src/admin/src/api/users.ts b/src/admin/src/api/users.ts index bac100e..0f8c041 100755 --- a/src/admin/src/api/users.ts +++ b/src/admin/src/api/users.ts @@ -2,20 +2,20 @@ import apiClient from './client'; import type { UserProfileResponse, UpdateProfileRequest, ChangePasswordRequest } from '@/types/api'; export async function getMe(): Promise { - const { data } = await apiClient.get('/v1/users/me'); + const { data } = await apiClient.get('/v1/user/me'); return data; } export async function updateMe(request: UpdateProfileRequest): Promise { - const { data } = await apiClient.put('/v1/users/me', request); + const { data } = await apiClient.put('/v1/user/me', request); return data; } export async function changePassword(request: ChangePasswordRequest): Promise { - await apiClient.post('/v1/users/me/change-password', request); + await apiClient.post('/v1/user/me/change-password', request); } export async function getUserById(id: number): Promise { - const { data } = await apiClient.get(`/v1/users/${id}`); + const { data } = await apiClient.get(`/v1/user/${id}`); return data; } diff --git a/src/admin/src/api/webhooks.ts b/src/admin/src/api/webhooks.ts index 2558f71..4a16af9 100755 --- a/src/admin/src/api/webhooks.ts +++ b/src/admin/src/api/webhooks.ts @@ -8,7 +8,7 @@ import type { // ─── Organization webhooks ──────────────────────────────────────────────────── export async function listOrgWebhooks(orgId: number): Promise { - const { data } = await apiClient.get(`/v1/organisations/${orgId}/webhooks`); + const { data } = await apiClient.get(`/v1/organisation/${orgId}/webhooks`); return data; } @@ -17,7 +17,7 @@ export async function createOrgWebhook( request: CreateWebhookRequest, ): Promise { const { data } = await apiClient.post( - `/v1/organisations/${orgId}/webhooks`, + `/v1/organisation/${orgId}/webhooks`, request, ); return data; @@ -29,21 +29,21 @@ export async function updateOrgWebhook( request: CreateWebhookRequest, ): Promise { const { data } = await apiClient.put( - `/v1/organisations/${orgId}/webhooks/${webhookId}`, + `/v1/organisation/${orgId}/webhook/${webhookId}`, request, ); return data; } export async function deleteOrgWebhook(orgId: number, webhookId: number): Promise { - await apiClient.delete(`/v1/organisations/${orgId}/webhooks/${webhookId}`); + await apiClient.delete(`/v1/organisation/${orgId}/webhook/${webhookId}`); } // ─── Environment webhooks ───────────────────────────────────────────────────── export async function listEnvWebhooks(envApiKey: string): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/webhooks`, + `/v1/environment/${envApiKey}/webhooks`, ); return data; } @@ -53,7 +53,7 @@ export async function createEnvWebhook( request: CreateWebhookRequest, ): Promise { const { data } = await apiClient.post( - `/v1/environments/${envApiKey}/webhooks`, + `/v1/environment/${envApiKey}/webhooks`, request, ); return data; @@ -65,14 +65,14 @@ export async function updateEnvWebhook( request: CreateWebhookRequest, ): Promise { const { data } = await apiClient.put( - `/v1/environments/${envApiKey}/webhooks/${webhookId}`, + `/v1/environment/${envApiKey}/webhook/${webhookId}`, request, ); return data; } export async function deleteEnvWebhook(envApiKey: string, webhookId: number): Promise { - await apiClient.delete(`/v1/environments/${envApiKey}/webhooks/${webhookId}`); + await apiClient.delete(`/v1/environment/${envApiKey}/webhook/${webhookId}`); } // ─── Delivery logs ──────────────────────────────────────────────────────────── @@ -82,7 +82,7 @@ export async function listWebhookDeliveries( webhookId: number, ): Promise { const { data } = await apiClient.get( - `/v1/environments/${envApiKey}/webhooks/${webhookId}/deliveries`, + `/v1/environment/${envApiKey}/webhook/${webhookId}/deliveries`, ); return data; } diff --git a/src/admin/src/components/features/FeatureDetail.vue b/src/admin/src/components/features/FeatureDetail.vue index 33fc737..d8e7cd4 100755 --- a/src/admin/src/components/features/FeatureDetail.vue +++ b/src/admin/src/components/features/FeatureDetail.vue @@ -95,7 +95,7 @@ - + @@ -558,6 +558,10 @@ async function onSaveSettings(): Promise { } } +function onTagsUpdated(updated: FeatureResponse): void { + emit('updated', updated); +} + async function onDelete(): Promise { if (!props.feature || deleteConfirmName.value !== props.feature.name) return; const projectId = contextStore.currentProject?.id; diff --git a/src/admin/src/components/features/TagsChipInput.vue b/src/admin/src/components/features/TagsChipInput.vue index 7846e1f..f2494ef 100755 --- a/src/admin/src/components/features/TagsChipInput.vue +++ b/src/admin/src/components/features/TagsChipInput.vue @@ -1,7 +1,7 @@