From 6c9eb8c2f215de4d25774bd0d01313912f55d9d1 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 25 Nov 2025 14:40:56 -0500 Subject: [PATCH 1/4] add route --- .../src/app/api/v1/user/referral/route.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/app/control/src/app/api/v1/user/referral/route.ts b/packages/app/control/src/app/api/v1/user/referral/route.ts index e11cd6898..452b1ad1a 100644 --- a/packages/app/control/src/app/api/v1/user/referral/route.ts +++ b/packages/app/control/src/app/api/v1/user/referral/route.ts @@ -3,12 +3,54 @@ import { z } from 'zod'; import { appIdSchema } from '@/services/db/apps/lib/schemas'; import { authRoute } from '../../../../../lib/api/auth-route'; import { setAppMembershipReferrer } from '@/services/db/apps/membership'; +import { + getUserAppReferralCode, + createAppReferralCode, +} from '@/services/db/apps/referral-code'; + +const getUserReferralCodeSchema = z.object({ + echoAppId: appIdSchema, +}); const setUserReferrerForAppSchema = z.object({ echoAppId: appIdSchema, code: z.string(), }); +export const GET = authRoute + .query(getUserReferralCodeSchema) + .handler(async (_, context) => { + const { echoAppId } = context.query; + const userId = context.ctx.userId; + + let referralCode = await getUserAppReferralCode(userId, echoAppId); + + if (!referralCode) { + referralCode = await createAppReferralCode(userId, { + appId: echoAppId, + expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + }); + + if (!referralCode) { + return NextResponse.json( + { + success: false, + message: 'Failed to create referral code', + }, + { status: 500 } + ); + } + } + + return NextResponse.json({ + success: true, + message: 'Referral code retrieved successfully', + code: referralCode.code, + referralLinkUrl: referralCode.referralLinkUrl, + expiresAt: referralCode.expiresAt, + }); + }); + export const POST = authRoute .body(setUserReferrerForAppSchema) .handler(async (_, context) => { From 974cccb4dee21c4e5be42a21153565611ada0172 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 25 Nov 2025 14:41:15 -0500 Subject: [PATCH 2/4] add integ tests --- packages/tests/integration/README.md | 83 +++++++- .../tests/integration/config/test-data.ts | 31 +++ .../scripts/seed-integration-db.ts | 13 ++ .../referral-code.client.test.ts | 195 ++++++++++++++++++ .../tests/integration/utils/api-client.ts | 61 ++++++ 5 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts diff --git a/packages/tests/integration/README.md b/packages/tests/integration/README.md index e5e0c8418..ad47c140c 100644 --- a/packages/tests/integration/README.md +++ b/packages/tests/integration/README.md @@ -1 +1,82 @@ -# Integration test trigger +# Integration Tests + +This package contains integration tests for the Echo platform. + +## Setup + +1. Set up the test environment: +```bash +pnpm env:setup +``` + +2. Seed the test database: +```bash +pnpm db:seed +``` + +## Running Tests + +Run all integration tests: +```bash +pnpm test:watch +``` + +Run specific test suites: +```bash +# Echo Data Server tests +pnpm test:echo-data-server + +# OAuth Protocol tests +pnpm test:oauth-protocol +``` + +## Test Suites + +### Echo Data Server Tests +Located in `tests/echo-data-server/`: +- `api-key.client.test.ts` - API key authentication and usage +- `402-auth.client.test.ts` - Payment required (402) authentication flow +- `echo-access-jwt.client.test.ts` - JWT token validation +- `free-tier.client.test.ts` - Free tier functionality +- `referral-code.client.test.ts` - Referral code creation and application +- `in-flight-requests.test.ts` - Concurrent request handling + +### OAuth Protocol Tests +Located in `tests/oauth-protocol/`: +- OAuth authorization flow +- Token refresh and lifecycle +- PKCE security +- CSRF vulnerability testing + +## Referral Code Tests + +The referral code integration tests (`referral-code.client.test.ts`) cover: + +1. **GET endpoint** - Retrieval of referral codes: + - Retrieving an existing referral code for a user + - Auto-creating a referral code for users who don't have one + - Ensuring consistency across multiple requests + +2. **POST endpoint** - Application of referral codes: + - Successfully applying another user's referral code + - Rejecting invalid referral codes + - Preventing users from applying codes when they already have a referrer + +### Test Data + +Referral code test data is defined in `config/test-data.ts`: +- Primary user has referral code: `TEST-REFERRAL-CODE-PRIMARY` +- Secondary user has referral code: `TEST-REFERRAL-CODE-SECONDARY` +- Tertiary user has no referral code (created during tests) + +## Database Management + +Reset and reseed the database: +```bash +pnpm db:reset-and-seed +``` + +Reset only: +```bash +pnpm db:reset +``` diff --git a/packages/tests/integration/config/test-data.ts b/packages/tests/integration/config/test-data.ts index c80b92ec0..7ef2dbae0 100644 --- a/packages/tests/integration/config/test-data.ts +++ b/packages/tests/integration/config/test-data.ts @@ -131,6 +131,26 @@ export const TEST_DATA = { }, }, + // Referral code configurations + referralCodes: { + primaryUserCode: { + id: '88888888-8888-4888-8888-888888888888', + code: 'TEST-REFERRAL-CODE-PRIMARY', + userId: '11111111-1111-4111-8111-111111111111', // Primary test user + expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year from now + isArchived: false, + usedAt: null, + }, + secondaryUserCode: { + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + code: 'TEST-REFERRAL-CODE-SECONDARY', + userId: '33333333-3333-4333-8333-333333333333', // Secondary test user + expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year from now + isArchived: false, + usedAt: null, + }, + }, + // Test timeouts and delays timeouts: { default: 30000, @@ -234,6 +254,16 @@ export const TEST_SPEND_POOL_IDS = { primary: TEST_DATA.spendPools.primary.id, }; +export const TEST_REFERRAL_CODE_IDS = { + primary: TEST_DATA.referralCodes.primaryUserCode.id, + secondary: TEST_DATA.referralCodes.secondaryUserCode.id, +}; + +export const TEST_REFERRAL_CODES = { + primary: TEST_DATA.referralCodes.primaryUserCode.code, + secondary: TEST_DATA.referralCodes.secondaryUserCode.code, +}; + // Type definitions for test data export type TestData = typeof TEST_DATA; export type TestUser = typeof TEST_DATA.users.primary; @@ -242,3 +272,4 @@ export type TestApiKey = typeof TEST_DATA.apiKeys.primary; export type TestSpendPool = typeof TEST_DATA.spendPools.primary; export type TestUserSpendPoolUsage = typeof TEST_DATA.userSpendPoolUsage.tertiaryUserPrimaryPool; +export type TestReferralCode = typeof TEST_DATA.referralCodes.primaryUserCode; diff --git a/packages/tests/integration/scripts/seed-integration-db.ts b/packages/tests/integration/scripts/seed-integration-db.ts index 161d3e42d..86555b85a 100644 --- a/packages/tests/integration/scripts/seed-integration-db.ts +++ b/packages/tests/integration/scripts/seed-integration-db.ts @@ -25,6 +25,7 @@ export async function seedIntegrationDatabase() { await prisma.spendPool.deleteMany(); await prisma.apiKey.deleteMany(); await prisma.appMembership.deleteMany(); + await prisma.referralCode.deleteMany(); await prisma.echoApp.deleteMany(); await prisma.user.deleteMany(); @@ -171,6 +172,17 @@ export async function seedIntegrationDatabase() { console.log('šŸ¤– Created test LLM transaction'); + // Create test referral codes + await prisma.referralCode.create({ + data: TEST_DATA.referralCodes.primaryUserCode, + }); + + await prisma.referralCode.create({ + data: TEST_DATA.referralCodes.secondaryUserCode, + }); + + console.log('šŸŽŸļø Created test referral codes'); + console.log('āœ… Integration test database seeded successfully'); console.log('\nšŸ“Š Summary:'); console.log(` - Users: 3`); @@ -181,6 +193,7 @@ export async function seedIntegrationDatabase() { console.log(` - User Spend Pool Usage: 1`); console.log(` - Payments: 1`); console.log(` - LLM Transactions: 1`); + console.log(` - Referral Codes: 2`); } catch (error) { console.error('āŒ Error seeding integration test database:', error); throw error; diff --git a/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts b/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts new file mode 100644 index 000000000..710c149cc --- /dev/null +++ b/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts @@ -0,0 +1,195 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import { TEST_CONFIG, TEST_DATA, TEST_CLIENT_IDS } from '@/config/index'; +import { echoControlApi } from '@/utils/api-client'; +import { + startOAuthFlow, + extractAuthorizationCodeFromUrl, + completeOAuthFlow, +} from '@/utils/auth-helpers'; + +describe('Referral Code Client', () => { + let primaryUserAccessToken: string; + let secondaryUserAccessToken: string; + let tertiaryUserAccessToken: string; + const testAppId = TEST_CLIENT_IDS.primary; + + beforeAll(async () => { + // Get access tokens for all three test users + // Primary user (has a referral code in seed data) + const primaryFlow = await startOAuthFlow({ + clientId: testAppId, + }); + const primaryAuthUrl = await echoControlApi.validateOAuthAuthorizeRequest({ + client_id: primaryFlow.clientId, + redirect_uri: primaryFlow.redirectUri, + state: primaryFlow.state, + code_challenge: primaryFlow.codeChallenge, + code_challenge_method: 'S256', + scope: primaryFlow.scope, + }); + const primaryAuthCode = + extractAuthorizationCodeFromUrl(primaryAuthUrl).code; + const primaryTokens = await completeOAuthFlow(primaryAuthCode, primaryFlow); + primaryUserAccessToken = primaryTokens.access_token; + + // Secondary user (also has a referral code in seed data) + const api2 = new (echoControlApi.constructor as any)( + TEST_CONFIG.services.echoControl, + 'test-user-2' + ); + const secondaryFlow = await startOAuthFlow({ + clientId: testAppId, + }); + const secondaryAuthUrl = await api2.validateOAuthAuthorizeRequest({ + client_id: secondaryFlow.clientId, + redirect_uri: secondaryFlow.redirectUri, + state: secondaryFlow.state, + code_challenge: secondaryFlow.codeChallenge, + code_challenge_method: 'S256', + scope: secondaryFlow.scope, + }); + const secondaryAuthCode = + extractAuthorizationCodeFromUrl(secondaryAuthUrl).code; + const secondaryTokens = await completeOAuthFlow( + secondaryAuthCode, + secondaryFlow + ); + secondaryUserAccessToken = secondaryTokens.access_token; + + // Tertiary user (does not have a referral code yet) + const api3 = new (echoControlApi.constructor as any)( + TEST_CONFIG.services.echoControl, + 'test-user-3' + ); + const tertiaryFlow = await startOAuthFlow({ + clientId: testAppId, + }); + const tertiaryAuthUrl = await api3.validateOAuthAuthorizeRequest({ + client_id: tertiaryFlow.clientId, + redirect_uri: tertiaryFlow.redirectUri, + state: tertiaryFlow.state, + code_challenge: tertiaryFlow.codeChallenge, + code_challenge_method: 'S256', + scope: tertiaryFlow.scope, + }); + const tertiaryAuthCode = + extractAuthorizationCodeFromUrl(tertiaryAuthUrl).code; + const tertiaryTokens = await completeOAuthFlow( + tertiaryAuthCode, + tertiaryFlow + ); + tertiaryUserAccessToken = tertiaryTokens.access_token; + }); + + test('should retrieve existing referral code for primary user', async () => { + const result = await echoControlApi.getUserReferralCode( + primaryUserAccessToken, + testAppId + ); + + expect(result.success).toBe(true); + expect(result.code).toBeDefined(); + expect(result.code).toBe(TEST_DATA.referralCodes.primaryUserCode.code); + expect(result.referralLinkUrl).toBeDefined(); + expect(result.referralLinkUrl).toContain(result.code); + expect(result.expiresAt).toBeDefined(); + + console.log('āœ… Primary user successfully retrieved their referral code'); + }); + + test('should create referral code for tertiary user who does not have one', async () => { + const result = await echoControlApi.getUserReferralCode( + tertiaryUserAccessToken, + testAppId + ); + + expect(result.success).toBe(true); + expect(result.code).toBeDefined(); + expect(result.referralLinkUrl).toBeDefined(); + expect(result.referralLinkUrl).toContain(result.code); + expect(result.expiresAt).toBeDefined(); + + console.log( + 'āœ… Tertiary user successfully created a new referral code', + result.code + ); + }); + + test('should apply referral code from primary user to secondary user membership', async () => { + const primaryReferralCode = await echoControlApi.getUserReferralCode( + primaryUserAccessToken, + testAppId + ); + + expect(primaryReferralCode.code).toBeDefined(); + + // Secondary user applies the primary user's referral code + const result = await echoControlApi.applyReferralCode( + secondaryUserAccessToken, + testAppId, + primaryReferralCode.code! + ); + + expect(result.success).toBe(true); + expect(result.message).toContain('successfully'); + + console.log( + 'āœ… Secondary user successfully applied primary user referral code' + ); + }); + + test('should reject applying referral code when user already has a referrer', async () => { + const tertiaryReferralCode = await echoControlApi.getUserReferralCode( + tertiaryUserAccessToken, + testAppId + ); + + expect(tertiaryReferralCode.code).toBeDefined(); + + // Secondary user already has a referrer from previous test + // Attempt to apply tertiary user's code should fail + await expect( + echoControlApi.applyReferralCode( + secondaryUserAccessToken, + testAppId, + tertiaryReferralCode.code! + ) + ).rejects.toThrow(); + + console.log( + 'āœ… System correctly rejected referral code application for user who already has a referrer' + ); + }); + + test('should reject invalid referral code', async () => { + const invalidCode = 'INVALID-REFERRAL-CODE-12345'; + + await expect( + echoControlApi.applyReferralCode( + tertiaryUserAccessToken, + testAppId, + invalidCode + ) + ).rejects.toThrow(); + + console.log('āœ… System correctly rejected invalid referral code'); + }); + + test('should get same referral code on multiple requests', async () => { + const firstResult = await echoControlApi.getUserReferralCode( + primaryUserAccessToken, + testAppId + ); + + const secondResult = await echoControlApi.getUserReferralCode( + primaryUserAccessToken, + testAppId + ); + + expect(firstResult.code).toBe(secondResult.code); + expect(firstResult.referralLinkUrl).toBe(secondResult.referralLinkUrl); + + console.log('āœ… Referral code is consistent across multiple requests'); + }); +}); + diff --git a/packages/tests/integration/utils/api-client.ts b/packages/tests/integration/utils/api-client.ts index a07e6f7d2..470c23131 100644 --- a/packages/tests/integration/utils/api-client.ts +++ b/packages/tests/integration/utils/api-client.ts @@ -258,6 +258,67 @@ export class EchoControlApiClient { }); return await echoClient.balance.getFreeBalance(echoAppId); } + + // Referral code endpoints + async getUserReferralCode( + authToken: string, + echoAppId: string + ): Promise<{ + success: boolean; + message: string; + code?: string; + referralLinkUrl?: string; + expiresAt?: string; + }> { + const url = `${this.baseUrl}/api/v1/user/referral?echoAppId=${echoAppId}`; + const response = await this.fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${authToken}`, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to get referral code: ${response.status} ${response.statusText}\n${errorText}` + ); + } + + return response.json(); + } + + async applyReferralCode( + authToken: string, + echoAppId: string, + code: string + ): Promise<{ + success: boolean; + message: string; + }> { + const url = `${this.baseUrl}/api/v1/user/referral`; + const response = await this.fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${authToken}`, + }, + body: JSON.stringify({ + echoAppId, + code, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to apply referral code: ${response.status} ${response.statusText}\n${errorText}` + ); + } + + return response.json(); + } } // Export a default instance with test configuration From f383b485a07e402c5974f56b7a829ec381f26b1b Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 25 Nov 2025 15:50:10 -0500 Subject: [PATCH 3/4] fix oauth flow --- .../referral-code.client.test.ts | 116 +++++++++++------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts b/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts index 710c149cc..6f162daca 100644 --- a/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts +++ b/packages/tests/integration/tests/echo-data-server/referral-code.client.test.ts @@ -1,10 +1,10 @@ import { describe, test, expect, beforeAll } from 'vitest'; import { TEST_CONFIG, TEST_DATA, TEST_CLIENT_IDS } from '@/config/index'; -import { echoControlApi } from '@/utils/api-client'; +import { echoControlApi, EchoControlApiClient } from '@/utils/api-client'; import { - startOAuthFlow, - extractAuthorizationCodeFromUrl, - completeOAuthFlow, + generateCodeVerifier, + generateCodeChallenge, + generateState, } from '@/utils/auth-helpers'; describe('Referral Code Client', () => { @@ -14,70 +14,94 @@ describe('Referral Code Client', () => { const testAppId = TEST_CLIENT_IDS.primary; beforeAll(async () => { - // Get access tokens for all three test users + // Get access tokens via OAuth for all three test users // Primary user (has a referral code in seed data) - const primaryFlow = await startOAuthFlow({ - clientId: testAppId, - }); + const codeVerifier1 = generateCodeVerifier(); + const codeChallenge1 = generateCodeChallenge(codeVerifier1); + const state1 = generateState(); + const primaryAuthUrl = await echoControlApi.validateOAuthAuthorizeRequest({ - client_id: primaryFlow.clientId, - redirect_uri: primaryFlow.redirectUri, - state: primaryFlow.state, - code_challenge: primaryFlow.codeChallenge, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + state: state1, + code_challenge: codeChallenge1, code_challenge_method: 'S256', - scope: primaryFlow.scope, + scope: 'llm:invoke offline_access', + prompt: 'none', + }); + + const primaryCallbackUrl = new URL(primaryAuthUrl); + const primaryAuthCode = primaryCallbackUrl.searchParams.get('code'); + expect(primaryAuthCode).toBeTruthy(); + + const primaryTokens = await echoControlApi.exchangeCodeForToken({ + code: primaryAuthCode!, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + code_verifier: codeVerifier1, }); - const primaryAuthCode = - extractAuthorizationCodeFromUrl(primaryAuthUrl).code; - const primaryTokens = await completeOAuthFlow(primaryAuthCode, primaryFlow); primaryUserAccessToken = primaryTokens.access_token; // Secondary user (also has a referral code in seed data) - const api2 = new (echoControlApi.constructor as any)( + const api2 = new EchoControlApiClient( TEST_CONFIG.services.echoControl, 'test-user-2' ); - const secondaryFlow = await startOAuthFlow({ - clientId: testAppId, - }); + const codeVerifier2 = generateCodeVerifier(); + const codeChallenge2 = generateCodeChallenge(codeVerifier2); + const state2 = generateState(); + const secondaryAuthUrl = await api2.validateOAuthAuthorizeRequest({ - client_id: secondaryFlow.clientId, - redirect_uri: secondaryFlow.redirectUri, - state: secondaryFlow.state, - code_challenge: secondaryFlow.codeChallenge, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + state: state2, + code_challenge: codeChallenge2, code_challenge_method: 'S256', - scope: secondaryFlow.scope, + scope: 'llm:invoke offline_access', + prompt: 'none', + }); + + const secondaryCallbackUrl = new URL(secondaryAuthUrl); + const secondaryAuthCode = secondaryCallbackUrl.searchParams.get('code'); + expect(secondaryAuthCode).toBeTruthy(); + + const secondaryTokens = await echoControlApi.exchangeCodeForToken({ + code: secondaryAuthCode!, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + code_verifier: codeVerifier2, }); - const secondaryAuthCode = - extractAuthorizationCodeFromUrl(secondaryAuthUrl).code; - const secondaryTokens = await completeOAuthFlow( - secondaryAuthCode, - secondaryFlow - ); secondaryUserAccessToken = secondaryTokens.access_token; // Tertiary user (does not have a referral code yet) - const api3 = new (echoControlApi.constructor as any)( + const api3 = new EchoControlApiClient( TEST_CONFIG.services.echoControl, 'test-user-3' ); - const tertiaryFlow = await startOAuthFlow({ - clientId: testAppId, - }); + const codeVerifier3 = generateCodeVerifier(); + const codeChallenge3 = generateCodeChallenge(codeVerifier3); + const state3 = generateState(); + const tertiaryAuthUrl = await api3.validateOAuthAuthorizeRequest({ - client_id: tertiaryFlow.clientId, - redirect_uri: tertiaryFlow.redirectUri, - state: tertiaryFlow.state, - code_challenge: tertiaryFlow.codeChallenge, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + state: state3, + code_challenge: codeChallenge3, code_challenge_method: 'S256', - scope: tertiaryFlow.scope, + scope: 'llm:invoke offline_access', + prompt: 'none', + }); + + const tertiaryCallbackUrl = new URL(tertiaryAuthUrl); + const tertiaryAuthCode = tertiaryCallbackUrl.searchParams.get('code'); + expect(tertiaryAuthCode).toBeTruthy(); + + const tertiaryTokens = await echoControlApi.exchangeCodeForToken({ + code: tertiaryAuthCode!, + client_id: testAppId, + redirect_uri: 'http://localhost:3000/callback', + code_verifier: codeVerifier3, }); - const tertiaryAuthCode = - extractAuthorizationCodeFromUrl(tertiaryAuthUrl).code; - const tertiaryTokens = await completeOAuthFlow( - tertiaryAuthCode, - tertiaryFlow - ); tertiaryUserAccessToken = tertiaryTokens.access_token; }); From f0d286d39beef850309d85f0a3869fb0191f8e26 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 25 Nov 2025 16:07:32 -0500 Subject: [PATCH 4/4] referrer --- .../control/src/services/db/apps/membership.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/app/control/src/services/db/apps/membership.ts b/packages/app/control/src/services/db/apps/membership.ts index bd88d7985..192c19928 100644 --- a/packages/app/control/src/services/db/apps/membership.ts +++ b/packages/app/control/src/services/db/apps/membership.ts @@ -163,21 +163,30 @@ export async function setAppMembershipReferrer( echoAppId: string, code: string ): Promise { - const appMembership = await db.appMembership.findUnique({ + // Get or create the app membership + const appMembership = await db.appMembership.upsert({ where: { userId_echoAppId: { userId, echoAppId, }, - referrerId: null, }, + create: { + userId, + echoAppId, + role: AppRole.CUSTOMER, + status: MembershipStatus.ACTIVE, + totalSpent: 0, + }, + update: {}, }); - if (appMembership) { - // If the user already has a referrer, return false + // Check if user already has a referrer + if (appMembership.referrerId) { return false; } + // Validate the referral code exists const referralCode = await db.referralCode.findUnique({ where: { code,