MVP for API Key authentication

This commit is contained in:
ian
2025-12-03 14:51:59 +07:00
parent 9413c442df
commit 347e0192e2
17 changed files with 733 additions and 49 deletions
+40
View File
@@ -0,0 +1,40 @@
import { db, table } from '~/db'
import { generateApiKey, generateSecretKey } from '~/helpers/signature'
import { DataAlreadyExistsError } from '~/helpers/errors'
import { CreateMerchantBody } from '../schema'
import { checkMerchantExists } from '../queries/query'
export async function createMerchant(body: CreateMerchantBody) {
// Check if merchant with same email or phone exists
const exists = await checkMerchantExists(body.contactEmail, body.contactPhone)
if (exists) {
throw new DataAlreadyExistsError('Merchant with this email or phone already exists')
}
// Generate keys
const apiKey = generateApiKey()
const secretKey = generateSecretKey()
// Insert new merchant
const result = await db
.insert(table.merchants)
.values({
merchantName: body.merchantName,
contactEmail: body.contactEmail,
contactPhone: body.contactPhone,
apiKey,
secretKey,
})
.returning({
merchantId: table.merchants.merchantId,
apiKey: table.merchants.apiKey,
secretKey: table.merchants.secretKey,
createdAt: table.merchants.createdAt,
})
if (result.length === 0) {
throw new Error('Failed to create merchant')
}
return result[0]
}
+12
View File
@@ -0,0 +1,12 @@
import { createMerchant } from './command'
import { CreateMerchantBody } from '../schema'
export default abstract class CreateMerchantService {
static async createMerchant(body: CreateMerchantBody): Promise<{
merchantId: string
apiKey: string
secretKey: string
}> {
return await createMerchant(body)
}
}
+11
View File
@@ -0,0 +1,11 @@
import { eq, or } from 'drizzle-orm'
import { db, table } from '~/db'
export async function checkMerchantExists(email: string, phone: string) {
const existingMerchant = await db.$count(table.merchants, or(
eq(table.merchants.contactEmail, email),
eq(table.merchants.contactPhone, phone),
))
return existingMerchant > 0
}
+41
View File
@@ -0,0 +1,41 @@
import { Elysia } from 'elysia'
import { createMerchantBody, createMerchantResponseSchema } from './schema'
import CreateMerchantService from './commands/service'
import { basicAuthMiddleware } from '~/middlewares/basicAuth'
import { DataAlreadyExistsError } from '~/helpers/errors'
export const router = new Elysia({
name: 'modules.auth',
detail: { tags: ['Auth'] },
prefix: '/auth',
})
.post('/get-key', async ({ body, headers, set }) => {
basicAuthMiddleware(headers, set)
try {
const result = await CreateMerchantService.createMerchant(body)
return {
status: 'success' as const,
message: 'Merchant created successfully' as const,
data: result,
}
} catch (error) {
if (error instanceof DataAlreadyExistsError) {
set.status = 409
return {
status: 'conflict' as const,
message: 'Merchant with this email or phone already exists' as const,
}
}
set.status = 500
return {
status: 'internal_error' as const,
message: 'Server error' as const,
}
}
}, {
body: createMerchantBody,
response: createMerchantResponseSchema,
})
export default router
+34
View File
@@ -0,0 +1,34 @@
import { z } from 'zod'
// Request body schema
export const createMerchantBody = z.object({
merchantName: z.string(),
contactEmail: z.string().email(),
contactPhone: z.string(),
})
export type CreateMerchantBody = z.infer<typeof createMerchantBody>
// Response schemas
export const createMerchantResponseSchema = {
200: z.object({
status: z.literal('success'),
message: z.literal('Merchant created successfully'),
data: z.object({
merchantId: z.string(),
apiKey: z.string(),
secretKey: z.string(),
}),
}),
400: z.object({
status: z.literal('invalid_request'),
message: z.literal('Invalid request format'),
}),
409: z.object({
status: z.literal('conflict'),
message: z.literal('Merchant with this email or phone already exists'),
}),
500: z.object({
status: z.literal('internal_error'),
message: z.literal('Server error'),
}),
}