feat: tiered costs feature + admin tier management + tag selection
Build & Push Docker Image / build (push) Successful in 6m52s

- costs: nullable notes (fix create without notes), drop renewalDate
  (schema + API + UI), gate POST/PATCH/DELETE to admin + costs feature
- feature middleware: admin-aware hasFeature + getEntitlements union;
  /auth/me and login return resolved entitlements
- users: tier enum (free/premium/enterprise) in create/update/list,
  admin UI tier select + tier badge
- tags: GET /tags/all, TagInput autocomplete in new/edit tool forms,
  feature suggestions on focus, query invalidation on create/update
- openapi: nullable ToolUpdate urls, ToolUpdate tier fields, listAllTags
- Dockerfile: push-force to drop renewal_date column
This commit is contained in:
opencode
2026-08-02 01:10:47 +02:00
parent cd4efd16f6
commit 01c70085db
27 changed files with 478 additions and 86 deletions
+19 -4
View File
@@ -105,8 +105,8 @@ export const UpdateToolBody = zod.object({
"name": zod.string().min(1).optional(),
"description": zod.string().optional(),
"category": zod.string().optional(),
"websiteUrl": zod.string().nullable().optional(),
"iconUrl": zod.string().nullable().optional(),
"websiteUrl": zod.string().nullish(),
"iconUrl": zod.string().nullish(),
"features": zod.array(zod.string()).optional(),
"tags": zod.array(zod.string()).optional()
})
@@ -288,6 +288,13 @@ export const ListAllFeaturesResponseItem = zod.string()
export const ListAllFeaturesResponse = zod.array(ListAllFeaturesResponseItem)
/**
* @summary List all distinct tag strings across all tools
*/
export const ListAllTagsResponseItem = zod.string()
export const ListAllTagsResponse = zod.array(ListAllTagsResponseItem)
/**
* @summary Get authentication mode (oidc or local)
*/
@@ -310,6 +317,8 @@ export const LocalLoginResponse = zod.object({
"name": zod.string().nullish(),
"preferredUsername": zod.string().nullish(),
"role": zod.enum(['admin', 'user']).optional(),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional(),
"entitlements": zod.array(zod.string()).optional(),
"isLocal": zod.boolean().optional()
})
@@ -323,6 +332,8 @@ export const GetMeResponse = zod.object({
"name": zod.string().nullish(),
"preferredUsername": zod.string().nullish(),
"role": zod.enum(['admin', 'user']).optional(),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional(),
"entitlements": zod.array(zod.string()).optional(),
"isLocal": zod.boolean().optional()
})
@@ -335,6 +346,7 @@ export const ListUsersResponseItem = zod.object({
"username": zod.string(),
"email": zod.string().nullish(),
"role": zod.enum(['admin', 'user']),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional(),
"createdAt": zod.coerce.date()
})
export const ListUsersResponse = zod.array(ListUsersResponseItem)
@@ -353,7 +365,8 @@ export const CreateUserBody = zod.object({
"username": zod.string().min(createUserBodyUsernameMin),
"password": zod.string().min(createUserBodyPasswordMin),
"email": zod.string().optional(),
"role": zod.enum(['admin', 'user']).optional()
"role": zod.enum(['admin', 'user']).optional(),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional()
})
@@ -365,7 +378,8 @@ export const UpdateUserParams = zod.object({
})
export const UpdateUserBody = zod.object({
"role": zod.enum(['admin', 'user'])
"role": zod.enum(['admin', 'user']).optional(),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional()
})
export const UpdateUserResponse = zod.object({
@@ -373,6 +387,7 @@ export const UpdateUserResponse = zod.object({
"username": zod.string(),
"email": zod.string().nullish(),
"role": zod.enum(['admin', 'user']),
"tier": zod.enum(['free', 'premium', 'enterprise']).optional(),
"createdAt": zod.coerce.date()
})