From dbe931ab00a262a53988be9fe7b61fb9e70f60b2 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:07:06 +0200 Subject: [PATCH] feat(js-sdk): Add API key (#8838) * feat(js-sdk): Add API key * address PR comments * Allow params to create + update --- .../dashboard/src/hooks/api/api-keys.tsx | 72 ++++++++------ .../dashboard/src/lib/client/api-keys.ts | 63 ------------ .../dashboard/src/lib/client/client.ts | 2 - .../api-key-sales-channel-section.tsx | 56 +++++------ .../api-key-management-detail/loader.ts | 4 +- .../use-api-key-management-table-query.tsx | 4 +- .../api-key-sales-channels-form.tsx | 31 +++--- packages/core/js-sdk/src/admin/api-key.ts | 99 +++++++++++++++++++ packages/core/js-sdk/src/admin/index.ts | 5 +- .../types/src/http/api-key/admin/index.ts | 2 + .../types/src/http/api-key/admin/payloads.ts | 14 +++ .../types/src/http/api-key/admin/queries.ts | 19 ++++ .../src/api/admin/api-keys/validators.ts | 1 + 13 files changed, 223 insertions(+), 149 deletions(-) delete mode 100644 packages/admin-next/dashboard/src/lib/client/api-keys.ts create mode 100644 packages/core/js-sdk/src/admin/api-key.ts create mode 100644 packages/core/types/src/http/api-key/admin/payloads.ts create mode 100644 packages/core/types/src/http/api-key/admin/queries.ts diff --git a/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx b/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx index 0cdacb0725..c9fdf8d2bc 100644 --- a/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx @@ -1,4 +1,5 @@ -import { AdminApiKeyListResponse, AdminApiKeyResponse } from "@medusajs/types" +import { HttpTypes } from "@medusajs/types" +import { FetchError } from "@medusajs/js-sdk" import { MutationOptions, QueryKey, @@ -7,11 +8,9 @@ import { useMutation, useQuery, } from "@tanstack/react-query" -import { client } from "../../lib/client" +import { sdk } from "../../lib/client" import { queryClient } from "../../lib/query-client" import { queryKeysFactory } from "../../lib/query-key-factory" -import { CreateApiKeyReq, UpdateApiKeyReq } from "../../types/api-payloads" -import { ApiKeyDeleteRes } from "../../types/api-responses" import { salesChannelsQueryKeys } from "./sales-channels" const API_KEYS_QUERY_KEY = "api_keys" as const @@ -19,14 +18,18 @@ export const apiKeysQueryKeys = queryKeysFactory(API_KEYS_QUERY_KEY) export const useApiKey = ( id: string, - query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions< + HttpTypes.AdminApiKeyResponse, + FetchError, + HttpTypes.AdminApiKeyResponse, + QueryKey + >, "queryKey" | "queryFn" > ) => { const { data, ...rest } = useQuery({ - queryFn: () => client.apiKeys.retrieve(id, query), + queryFn: () => sdk.admin.apiKey.retrieve(id), queryKey: apiKeysQueryKeys.detail(id), ...options, }) @@ -35,19 +38,19 @@ export const useApiKey = ( } export const useApiKeys = ( - query?: Record, + query?: HttpTypes.AdminGetApiKeysParams, options?: Omit< UseQueryOptions< - AdminApiKeyListResponse, - Error, - AdminApiKeyListResponse, + HttpTypes.AdminGetApiKeysParams, + FetchError, + HttpTypes.AdminApiKeyListResponse, QueryKey >, "queryKey" | "queryFn" > ) => { const { data, ...rest } = useQuery({ - queryFn: () => client.apiKeys.list(query), + queryFn: () => sdk.admin.apiKey.list(query), queryKey: apiKeysQueryKeys.list(query), ...options, }) @@ -56,10 +59,14 @@ export const useApiKeys = ( } export const useCreateApiKey = ( - options?: UseMutationOptions + options?: UseMutationOptions< + HttpTypes.AdminApiKeyResponse, + FetchError, + HttpTypes.AdminCreateApiKey + > ) => { return useMutation({ - mutationFn: (payload) => client.apiKeys.create(payload), + mutationFn: (payload) => sdk.admin.apiKey.create(payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) @@ -71,10 +78,14 @@ export const useCreateApiKey = ( export const useUpdateApiKey = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions< + HttpTypes.AdminApiKeyResponse, + FetchError, + HttpTypes.AdminUpdateApiKey + > ) => { return useMutation({ - mutationFn: (payload) => client.apiKeys.update(id, payload), + mutationFn: (payload) => sdk.admin.apiKey.update(id, payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) @@ -87,10 +98,10 @@ export const useUpdateApiKey = ( export const useRevokeApiKey = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions ) => { return useMutation({ - mutationFn: () => client.apiKeys.revoke(id), + mutationFn: () => sdk.admin.apiKey.revoke(id), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) @@ -102,10 +113,14 @@ export const useRevokeApiKey = ( export const useDeleteApiKey = ( id: string, - options?: MutationOptions + options?: MutationOptions< + HttpTypes.DeleteResponse<"api_key">, + FetchError, + void + > ) => { return useMutation({ - mutationFn: () => client.apiKeys.delete(id), + mutationFn: () => sdk.admin.apiKey.delete(id), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) @@ -118,14 +133,14 @@ export const useDeleteApiKey = ( export const useBatchRemoveSalesChannelsFromApiKey = ( id: string, options?: UseMutationOptions< - AdminApiKeyResponse, - Error, - { sales_channel_ids: string[] } + HttpTypes.AdminApiKeyResponse, + FetchError, + HttpTypes.AdminBatchLink["remove"] > ) => { return useMutation({ mutationFn: (payload) => - client.apiKeys.batchRemoveSalesChannels(id, payload), + sdk.admin.apiKey.batchSalesChannels(id, { remove: payload }), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) @@ -142,13 +157,14 @@ export const useBatchRemoveSalesChannelsFromApiKey = ( export const useBatchAddSalesChannelsToApiKey = ( id: string, options?: UseMutationOptions< - AdminApiKeyResponse, - Error, - { sales_channel_ids: string[] } + HttpTypes.AdminApiKeyResponse, + FetchError, + HttpTypes.AdminBatchLink["add"] > ) => { return useMutation({ - mutationFn: (payload) => client.apiKeys.batchAddSalesChannels(id, payload), + mutationFn: (payload) => + sdk.admin.apiKey.batchSalesChannels(id, { add: payload }), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) diff --git a/packages/admin-next/dashboard/src/lib/client/api-keys.ts b/packages/admin-next/dashboard/src/lib/client/api-keys.ts deleted file mode 100644 index 0dc25a2e50..0000000000 --- a/packages/admin-next/dashboard/src/lib/client/api-keys.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AdminApiKeyListResponse, AdminApiKeyResponse } from "@medusajs/types" -import { CreateApiKeyReq, UpdateApiKeyReq } from "../../types/api-payloads" -import { ApiKeyDeleteRes } from "../../types/api-responses" -import { deleteRequest, getRequest, postRequest } from "./common" - -const retrieveApiKey = async (id: string, query?: Record) => { - return getRequest(`/admin/api-keys/${id}`, query) -} - -const listApiKeys = async (query?: Record) => { - return getRequest(`/admin/api-keys`, query) -} - -const deleteApiKey = async (id: string) => { - return deleteRequest(`/admin/api-keys/${id}`) -} - -const revokeApiKey = async (id: string) => { - return postRequest(`/admin/api-keys/${id}/revoke`) -} - -const createApiKey = async (payload: CreateApiKeyReq) => { - return postRequest(`/admin/api-keys`, payload) -} - -const updateApiKey = async (id: string, payload: UpdateApiKeyReq) => { - return postRequest(`/admin/api-keys/${id}`, payload) -} - -const batchRemoveSalesChannelsFromApiKey = async ( - id: string, - payload: { sales_channel_ids: string[] } -) => { - return postRequest( - `/admin/api-keys/${id}/sales-channels`, - { - remove: payload.sales_channel_ids, - } - ) -} - -const batchAddSalesChannelsFromApiKey = async ( - id: string, - payload: { sales_channel_ids: string[] } -) => { - return postRequest( - `/admin/api-keys/${id}/sales-channels`, - { - add: payload.sales_channel_ids, - } - ) -} - -export const apiKeys = { - retrieve: retrieveApiKey, - list: listApiKeys, - delete: deleteApiKey, - create: createApiKey, - update: updateApiKey, - revoke: revokeApiKey, - batchRemoveSalesChannels: batchRemoveSalesChannelsFromApiKey, - batchAddSalesChannels: batchAddSalesChannelsFromApiKey, -} diff --git a/packages/admin-next/dashboard/src/lib/client/client.ts b/packages/admin-next/dashboard/src/lib/client/client.ts index a95c1619e5..4fce5e6232 100644 --- a/packages/admin-next/dashboard/src/lib/client/client.ts +++ b/packages/admin-next/dashboard/src/lib/client/client.ts @@ -1,5 +1,4 @@ import Medusa from "@medusajs/js-sdk" -import { apiKeys } from "./api-keys" import { campaigns } from "./campaigns" import { categories } from "./categories" import { currencies } from "./currencies" @@ -24,7 +23,6 @@ import { workflowExecutions } from "./workflow-executions" export const backendUrl = __BACKEND_URL__ ?? "http://localhost:9000" export const client = { - apiKeys: apiKeys, campaigns: campaigns, categories: categories, customerGroups: customerGroups, diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx index 8a900831ea..a42a294df7 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx @@ -76,24 +76,19 @@ export const ApiKeySalesChannelSection = ({ return } - await mutateAsync( - { - sales_channel_ids: keys, + await mutateAsync(keys, { + onSuccess: () => { + toast.success( + t("apiKeyManagement.removeSalesChannel.successToastBatch", { + count: keys.length, + }) + ) + setRowSelection({}) }, - { - onSuccess: () => { - toast.success( - t("apiKeyManagement.removeSalesChannel.successToastBatch", { - count: keys.length, - }) - ) - setRowSelection({}) - }, - onError: (err) => { - toast.error(err.message) - }, - } - ) + onError: (err) => { + toast.error(err.message) + }, + }) } return ( @@ -167,23 +162,18 @@ const SalesChannelActions = ({ return } - await mutateAsync( - { - sales_channel_ids: [salesChannel.id], + await mutateAsync([salesChannel.id], { + onSuccess: () => { + toast.success( + t("apiKeyManagement.removeSalesChannel.successToast", { + count: 1, + }) + ) }, - { - onSuccess: () => { - toast.success( - t("apiKeyManagement.removeSalesChannel.successToast", { - count: 1, - }) - ) - }, - onError: (err) => { - toast.error(err.message) - }, - } - ) + onError: (err) => { + toast.error(err.message) + }, + }) } return ( diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts index 408764e522..c71e023a86 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts @@ -2,12 +2,12 @@ import { LoaderFunctionArgs } from "react-router-dom" import { AdminApiKeyResponse } from "@medusajs/types" import { apiKeysQueryKeys } from "../../../hooks/api/api-keys" -import { client } from "../../../lib/client" +import { sdk } from "../../../lib/client" import { queryClient } from "../../../lib/query-client" const apiKeyDetailQuery = (id: string) => ({ queryKey: apiKeysQueryKeys.detail(id), - queryFn: async () => client.apiKeys.retrieve(id), + queryFn: async () => sdk.admin.apiKey.retrieve(id), }) export const apiKeyLoader = async ({ params }: LoaderFunctionArgs) => { diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-query.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-query.tsx index c1fc542f43..d741deca73 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-query.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-query.tsx @@ -1,4 +1,4 @@ -import { GetPublishableApiKeysParams } from "@medusajs/medusa" +import { HttpTypes } from "@medusajs/types" import { useQueryParams } from "../../../../../hooks/use-query-params" type UseApiKeyManagementTableQueryProps = { @@ -17,7 +17,7 @@ export const useApiKeyManagementTableQuery = ({ const { offset, created_at, updated_at, revoked_at, q, order } = queryObject - const searchParams: GetPublishableApiKeysParams = { + const searchParams: HttpTypes.AdminGetApiKeysParams = { limit: pageSize, offset: offset ? Number(offset) : 0, created_at: created_at ? JSON.parse(created_at) : undefined, diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx index 4b8f23f0a6..a519cb821a 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx @@ -96,25 +96,20 @@ export const ApiKeySalesChannelsForm = ({ }) const handleSubmit = form.handleSubmit(async (values) => { - await mutateAsync( - { - sales_channel_ids: values.sales_channel_ids, - }, - { - onSuccess: () => { - toast.success( - t("apiKeyManagement.salesChannels.successToast", { - count: values.sales_channel_ids.length, - }) - ) + await mutateAsync(values.sales_channel_ids, { + onSuccess: () => { + toast.success( + t("apiKeyManagement.salesChannels.successToast", { + count: values.sales_channel_ids.length, + }) + ) - handleSuccess() - }, - onError: (err) => { - toast.error(err.message) - }, - } - ) + handleSuccess() + }, + onError: (err) => { + toast.error(err.message) + }, + }) }) return ( diff --git a/packages/core/js-sdk/src/admin/api-key.ts b/packages/core/js-sdk/src/admin/api-key.ts new file mode 100644 index 0000000000..8f481fa42b --- /dev/null +++ b/packages/core/js-sdk/src/admin/api-key.ts @@ -0,0 +1,99 @@ +import { HttpTypes, PaginatedResponse } from "@medusajs/types" +import { Client } from "../client" +import { ClientHeaders } from "../types" + +export class ApiKey { + private client: Client + constructor(client: Client) { + this.client = client + } + + async list( + queryParams?: HttpTypes.AdminGetApiKeysParams, + headers?: ClientHeaders + ) { + return await this.client.fetch< + PaginatedResponse + >(`/admin/api-keys`, { + query: queryParams, + headers, + }) + } + + async create( + body: HttpTypes.AdminCreateApiKey, + query?: HttpTypes.AdminGetApiKeysParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/api-keys`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async revoke(id: string, headers?: ClientHeaders) { + return await this.client.fetch( + `/admin/api-keys/${id}`, + { + method: "DELETE", + headers, + } + ) + } + + async retrieve(id: string, headers?: ClientHeaders) { + return await this.client.fetch( + `/admin/api-keys/${id}`, + { + headers, + } + ) + } + + async update( + id: string, + body: HttpTypes.AdminUpdateApiKey, + query?: HttpTypes.AdminGetApiKeysParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/api-keys/${id}`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async delete(id: string, headers?: ClientHeaders) { + return await this.client.fetch>( + `/admin/api-keys/${id}`, + { + method: "DELETE", + headers, + } + ) + } + + async batchSalesChannels( + id: string, + body: HttpTypes.AdminBatchLink, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/api-keys/${id}/sales-channels`, + { + method: "POST", + headers, + body, + } + ) + } +} diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts index 25323f6252..1600ab2b82 100644 --- a/packages/core/js-sdk/src/admin/index.ts +++ b/packages/core/js-sdk/src/admin/index.ts @@ -1,4 +1,5 @@ import { Client } from "../client" +import { ApiKey } from "./api-key" import { Claim } from "./claim" import { Currency } from "./currency" import { Customer } from "./customer" @@ -10,6 +11,7 @@ import { InventoryItem } from "./inventory-item" import { Invite } from "./invite" import { Notification } from "./notification" import { Order } from "./order" +import { OrderEdit } from "./order-edit" import { Payment } from "./payment" import { PaymentCollection } from "./payment-collection" import { PriceList } from "./price-list" @@ -33,7 +35,6 @@ import { TaxRate } from "./tax-rate" import { TaxRegion } from "./tax-region" import { Upload } from "./upload" import { User } from "./user" -import { OrderEdit } from "./order-edit" export class Admin { public invite: Invite @@ -71,6 +72,7 @@ export class Admin { public productVariant: ProductVariant public refundReason: RefundReason public paymentCollection: PaymentCollection + public apiKey: ApiKey constructor(client: Client) { this.invite = new Invite(client) @@ -108,5 +110,6 @@ export class Admin { this.refundReason = new RefundReason(client) this.exchange = new Exchange(client) this.paymentCollection = new PaymentCollection(client) + this.apiKey = new ApiKey(client) } } diff --git a/packages/core/types/src/http/api-key/admin/index.ts b/packages/core/types/src/http/api-key/admin/index.ts index 277e322691..cfa3aa8953 100644 --- a/packages/core/types/src/http/api-key/admin/index.ts +++ b/packages/core/types/src/http/api-key/admin/index.ts @@ -1 +1,3 @@ export * from "./api-key" +export * from "./payloads" +export * from "./queries" diff --git a/packages/core/types/src/http/api-key/admin/payloads.ts b/packages/core/types/src/http/api-key/admin/payloads.ts new file mode 100644 index 0000000000..cd2f8e7a30 --- /dev/null +++ b/packages/core/types/src/http/api-key/admin/payloads.ts @@ -0,0 +1,14 @@ +import { ApiKeyType } from "../../../api-key" + +export interface AdminCreateApiKey { + title: string + type: ApiKeyType +} + +export interface AdminUpdateApiKey { + title: string +} + +export interface AdminRevokeApiKey { + revoke_in?: number +} diff --git a/packages/core/types/src/http/api-key/admin/queries.ts b/packages/core/types/src/http/api-key/admin/queries.ts new file mode 100644 index 0000000000..dea17f93a0 --- /dev/null +++ b/packages/core/types/src/http/api-key/admin/queries.ts @@ -0,0 +1,19 @@ +import { ApiKeyType } from "../../../api-key" +import { BaseFilterable, OperatorMap } from "../../../dal" +import { FindParams } from "../../common" + +export interface AdminGetApiKeysParams + extends FindParams, + BaseFilterable { + q?: string + id?: string | string[] + title?: string | string[] + token?: string | string[] + type?: ApiKeyType + created_at?: OperatorMap + updated_at?: OperatorMap + deleted_at?: OperatorMap + revoked_at?: OperatorMap + $and?: AdminGetApiKeysParams[] + $or?: AdminGetApiKeysParams[] +} diff --git a/packages/medusa/src/api/admin/api-keys/validators.ts b/packages/medusa/src/api/admin/api-keys/validators.ts index 7b437a5e19..8923451331 100644 --- a/packages/medusa/src/api/admin/api-keys/validators.ts +++ b/packages/medusa/src/api/admin/api-keys/validators.ts @@ -22,6 +22,7 @@ export const AdminGetApiKeysParams = createFindParams({ created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), + revoked_at: createOperatorMap().optional(), $and: z.lazy(() => AdminGetApiKeysParams.array()).optional(), $or: z.lazy(() => AdminGetApiKeysParams.array()).optional(), })