add frontend support for api token create, delete and revoke

This commit is contained in:
Gregor Vostrak
2025-02-11 17:57:00 +01:00
committed by Constantin Graf
parent bbed618fdc
commit 4ea55e5867
9 changed files with 488 additions and 41 deletions

View File

@@ -1,6 +1,21 @@
import { makeApi, Zodios, type ZodiosOptions } from '@zodios/core';
import { z } from 'zod';
const ApiTokenResource = z
.object({
id: z.string(),
name: z.string(),
revoked: z.string(),
scopes: z.string(),
created_at: z.union([z.string(), z.null()]),
expires_at: z.union([z.string(), z.null()]),
})
.passthrough();
const ApiTokenCollection = z.array(ApiTokenResource);
const ApiTokenStoreRequest = z
.object({ name: z.string().min(1).max(255) })
.passthrough();
const ApiTokenWithAccessTokenResource = z.string();
const ClientResource = z
.object({
id: z.string(),
@@ -26,9 +41,11 @@ const ImportRequest = z
const InvitationResource = z
.object({ id: z.string(), email: z.string(), role: z.string() })
.passthrough();
const Role = z.enum(['owner', 'admin', 'manager', 'employee', 'placeholder']);
const InvitationStoreRequest = z
.object({ email: z.string().email(), role: Role })
.object({
email: z.string().email(),
role: z.enum(['admin', 'manager', 'employee']),
})
.passthrough();
const MemberResource = z
.object({
@@ -41,6 +58,7 @@ const MemberResource = z
billable_rate: z.union([z.number(), z.null()]),
})
.passthrough();
const Role = z.enum(['owner', 'admin', 'manager', 'employee', 'placeholder']);
const MemberUpdateRequest = z
.object({ role: Role, billable_rate: z.union([z.number(), z.null()]) })
.partial()
@@ -190,13 +208,6 @@ const ReportStoreRequest = z
timezone: z.union([z.string(), z.null()]).optional(),
})
.passthrough(),
'properties.member_ids': z.string().optional(),
'properties.client_ids': z.string().optional(),
'properties.project_ids': z.string().optional(),
'properties.tag_ids': z.string().optional(),
'properties.task_ids': z.string().optional(),
'properties.week_start': z.string().optional(),
'properties.timezone': z.string().optional(),
})
.passthrough();
const DetailedReportResource = z
@@ -459,18 +470,21 @@ const PersonalMembershipResource = z
role: z.string(),
})
.passthrough();
const PersonalMembershipCollection = z.array(PersonalMembershipResource);
export const schemas = {
ApiTokenResource,
ApiTokenCollection,
ApiTokenStoreRequest,
ApiTokenWithAccessTokenResource,
ClientResource,
ClientCollection,
ClientStoreRequest,
ClientUpdateRequest,
ImportRequest,
InvitationResource,
Role,
InvitationStoreRequest,
MemberResource,
Role,
MemberUpdateRequest,
OrganizationResource,
OrganizationUpdateRequest,
@@ -502,7 +516,6 @@ export const schemas = {
TimeEntryUpdateRequest,
UserResource,
PersonalMembershipResource,
PersonalMembershipCollection,
};
const endpoints = makeApi([
@@ -786,11 +799,6 @@ const endpoints = makeApi([
alias: 'exportOrganization',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z.object({}).partial().passthrough(),
},
{
name: 'organization',
type: 'Path',
@@ -1122,11 +1130,6 @@ const endpoints = makeApi([
alias: 'resendInvitationEmail',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z.object({}).partial().passthrough(),
},
{
name: 'organization',
type: 'Path',
@@ -1345,11 +1348,6 @@ const endpoints = makeApi([
alias: 'invitePlaceholder',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z.object({}).partial().passthrough(),
},
{
name: 'organization',
type: 'Path',
@@ -1397,11 +1395,6 @@ const endpoints = makeApi([
alias: 'v1.members.make-placeholder',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z.object({}).partial().passthrough(),
},
{
name: 'organization',
type: 'Path',
@@ -2614,6 +2607,11 @@ Users with the permission `time-entries:view:own` can only use this en
type: 'Query',
schema: z.enum(['true', 'false']).optional(),
},
{
name: 'user_id',
type: 'Query',
schema: z.string().optional(),
},
{
name: 'member_ids',
type: 'Query',
@@ -2639,11 +2637,6 @@ Users with the permission `time-entries:view:own` can only use this en
type: 'Query',
schema: z.array(z.string()).min(1).optional(),
},
{
name: 'user_id',
type: 'Query',
schema: z.string().optional(),
},
],
response: z
.object({
@@ -3438,6 +3431,120 @@ The report is considered public if the `is_public` field is set to &#x
},
],
},
{
method: 'get',
path: '/v1/users/me/api-tokens',
alias: 'getApiTokens',
description: `This endpoint is independent of organization.`,
requestFormat: 'json',
response: z.object({ data: ApiTokenCollection }).passthrough(),
errors: [
{
status: 401,
description: `Unauthenticated`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
],
},
{
method: 'post',
path: '/v1/users/me/api-tokens',
alias: 'createApiToken',
description: `The response will contain the access token that can be used to send authenticated API requests.
Please note that the access token is only shown in this response and cannot be retrieved later.`,
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z
.object({ name: z.string().min(1).max(255) })
.passthrough(),
},
],
response: z
.object({ data: ApiTokenWithAccessTokenResource })
.passthrough(),
errors: [
{
status: 401,
description: `Unauthenticated`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 422,
description: `Validation error`,
schema: z
.object({
message: z.string(),
errors: z.record(z.array(z.string())),
})
.passthrough(),
},
],
},
{
method: 'delete',
path: '/v1/users/me/api-tokens/:apiTokenId',
alias: 'deleteApiToken',
requestFormat: 'json',
parameters: [
{
name: 'apiTokenId',
type: 'Path',
schema: z.string(),
},
],
response: z.null(),
errors: [
{
status: 401,
description: `Unauthenticated`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
],
},
{
method: 'post',
path: '/v1/users/me/api-tokens/:apiTokenId/revoke',
alias: 'revokeApiToken',
requestFormat: 'json',
parameters: [
{
name: 'apiTokenId',
type: 'Path',
schema: z.string(),
},
],
response: z.null(),
errors: [
{
status: 401,
description: `Unauthenticated`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
],
},
{
method: 'get',
path: '/v1/users/me/memberships',
@@ -3445,7 +3552,7 @@ The report is considered public if the `is_public` field is set to &#x
description: `This endpoint is independent of organization.`,
requestFormat: 'json',
response: z
.object({ data: PersonalMembershipCollection })
.object({ data: z.array(PersonalMembershipResource) })
.passthrough(),
errors: [
{