This commit is contained in:
+1
-1
Submodule graphql-contracts updated: 2080dce5b4...5ff41d56f8
@@ -0,0 +1,11 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "kyc_requests" ADD COLUMN "team_uuid" VARCHAR(100);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "kyc_monitoring" ADD COLUMN "team_uuid" VARCHAR(100);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "kyc_requests_team_uuid_idx" ON "kyc_requests"("team_uuid");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "kyc_monitoring_team_uuid_idx" ON "kyc_monitoring"("team_uuid");
|
||||||
@@ -10,6 +10,7 @@ model KYCApplication {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
uuid String @unique @default(uuid())
|
uuid String @unique @default(uuid())
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
|
teamUuid String? @map("team_uuid") @db.VarChar(100)
|
||||||
teamName String @default("") @map("team_name") @db.VarChar(200)
|
teamName String @default("") @map("team_name") @db.VarChar(200)
|
||||||
countryCode String @default("") @map("country_code") @db.VarChar(2)
|
countryCode String @default("") @map("country_code") @db.VarChar(2)
|
||||||
workflowStatus String @default("pending") @map("workflow_status") @db.VarChar(20)
|
workflowStatus String @default("pending") @map("workflow_status") @db.VarChar(20)
|
||||||
@@ -25,6 +26,7 @@ model KYCApplication {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
|
@@index([teamUuid])
|
||||||
@@map("kyc_requests")
|
@@map("kyc_requests")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ model KYCProfile {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
uuid String @unique @default(uuid())
|
uuid String @unique @default(uuid())
|
||||||
userId String @map("user_id") @db.VarChar(255)
|
userId String @map("user_id") @db.VarChar(255)
|
||||||
|
teamUuid String? @map("team_uuid") @db.VarChar(100)
|
||||||
teamName String @default("") @map("team_name") @db.VarChar(200)
|
teamName String @default("") @map("team_name") @db.VarChar(200)
|
||||||
countryCode String @default("") @map("country_code") @db.VarChar(2)
|
countryCode String @default("") @map("country_code") @db.VarChar(2)
|
||||||
workflowStatus String @default("pending") @map("workflow_status") @db.VarChar(20)
|
workflowStatus String @default("pending") @map("workflow_status") @db.VarChar(20)
|
||||||
@@ -47,6 +50,7 @@ model KYCProfile {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
|
@@index([teamUuid])
|
||||||
@@map("kyc_monitoring")
|
@@map("kyc_monitoring")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-21
@@ -3,7 +3,7 @@ import { prisma } from '../db.js'
|
|||||||
|
|
||||||
export const m2mTypeDefs = `#graphql
|
export const m2mTypeDefs = `#graphql
|
||||||
type KycPricingContext {
|
type KycPricingContext {
|
||||||
userId: String!
|
teamUuid: String!
|
||||||
profileUuid: String!
|
profileUuid: String!
|
||||||
countryCode: String!
|
countryCode: String!
|
||||||
resolvedAt: String!
|
resolvedAt: String!
|
||||||
@@ -17,19 +17,19 @@ export const m2mTypeDefs = `#graphql
|
|||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
health: String!
|
health: String!
|
||||||
pricingContextForUser(userId: String!): KycPricingContext
|
pricingContextForTeam(teamUuid: String!): KycPricingContext
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createKycProfile(kycApplicationId: String!): CreateKycProfileResult
|
createKycProfile(kycApplicationId: String!): CreateKycProfileResult
|
||||||
createKycMonitoring(kycApplicationId: String!): CreateKycProfileResult
|
createKycMonitoring(kycApplicationId: String!): CreateKycProfileResult
|
||||||
ensureDemoPricingContextForUser(userId: String!, countryCode: String!): KycPricingContext!
|
ensureDemoPricingContextForTeam(teamUuid: String!, userId: String!, countryCode: String!): KycPricingContext!
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
function demoProfileUuid(userId: string): string {
|
function demoProfileUuid(teamUuid: string): string {
|
||||||
const hex = createHash('sha256')
|
const hex = createHash('sha256')
|
||||||
.update(`kyc:demo-pricing-context:${userId}`)
|
.update(`kyc:demo-pricing-context:${teamUuid}`)
|
||||||
.digest('hex')
|
.digest('hex')
|
||||||
.slice(0, 32)
|
.slice(0, 32)
|
||||||
.split('')
|
.split('')
|
||||||
@@ -48,16 +48,19 @@ function normalizedCountryCode(countryCode: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapPricingContext(profile: {
|
function mapPricingContext(profile: {
|
||||||
userId: string
|
teamUuid: string | null
|
||||||
uuid: string
|
uuid: string
|
||||||
countryCode: string
|
countryCode: string
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
}) {
|
}) {
|
||||||
|
if (profile.teamUuid === null || profile.teamUuid.length === 0) {
|
||||||
|
throw new Error(`KYC pricing profile ${profile.uuid} has no team`)
|
||||||
|
}
|
||||||
if (profile.countryCode.length !== 2) {
|
if (profile.countryCode.length !== 2) {
|
||||||
throw new Error(`KYC pricing profile ${profile.uuid} has no valid country code`)
|
throw new Error(`KYC pricing profile ${profile.uuid} has no valid country code`)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
userId: profile.userId,
|
teamUuid: profile.teamUuid,
|
||||||
profileUuid: profile.uuid,
|
profileUuid: profile.uuid,
|
||||||
countryCode: profile.countryCode.toUpperCase(),
|
countryCode: profile.countryCode.toUpperCase(),
|
||||||
resolvedAt: profile.updatedAt.toISOString(),
|
resolvedAt: profile.updatedAt.toISOString(),
|
||||||
@@ -65,7 +68,6 @@ function mapPricingContext(profile: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createProfile(_: unknown, args: { kycApplicationId: string }) {
|
async function createProfile(_: unknown, args: { kycApplicationId: string }) {
|
||||||
try {
|
|
||||||
const app = await prisma.kYCApplication.findUnique({
|
const app = await prisma.kYCApplication.findUnique({
|
||||||
where: { uuid: args.kycApplicationId },
|
where: { uuid: args.kycApplicationId },
|
||||||
})
|
})
|
||||||
@@ -73,10 +75,12 @@ async function createProfile(_: unknown, args: { kycApplicationId: string }) {
|
|||||||
if (!app) {
|
if (!app) {
|
||||||
return { success: false, profileUuid: '', message: `KYCApplication not found: ${args.kycApplicationId}` }
|
return { success: false, profileUuid: '', message: `KYCApplication not found: ${args.kycApplicationId}` }
|
||||||
}
|
}
|
||||||
|
if (app.teamUuid === null || app.teamUuid.length === 0) {
|
||||||
|
throw new Error(`KYCApplication ${app.uuid} has no team`)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if profile already exists
|
|
||||||
const existing = await prisma.kYCProfile.findFirst({
|
const existing = await prisma.kYCProfile.findFirst({
|
||||||
where: { userId: app.userId, teamName: app.teamName },
|
where: { teamUuid: app.teamUuid },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -86,6 +90,7 @@ async function createProfile(_: unknown, args: { kycApplicationId: string }) {
|
|||||||
const profile = await prisma.kYCProfile.create({
|
const profile = await prisma.kYCProfile.create({
|
||||||
data: {
|
data: {
|
||||||
userId: app.userId,
|
userId: app.userId,
|
||||||
|
teamUuid: app.teamUuid,
|
||||||
teamName: app.teamName,
|
teamName: app.teamName,
|
||||||
countryCode: app.countryCode,
|
countryCode: app.countryCode,
|
||||||
workflowStatus: 'active',
|
workflowStatus: 'active',
|
||||||
@@ -101,15 +106,12 @@ async function createProfile(_: unknown, args: { kycApplicationId: string }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return { success: true, profileUuid: profile.uuid, message: 'Profile created' }
|
return { success: true, profileUuid: profile.uuid, message: 'Profile created' }
|
||||||
} catch (e) {
|
|
||||||
return { success: false, profileUuid: '', message: String(e) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pricingContextForUser(_: unknown, args: { userId: string }) {
|
async function pricingContextForTeam(_: unknown, args: { teamUuid: string }) {
|
||||||
const profile = await prisma.kYCProfile.findFirst({
|
const profile = await prisma.kYCProfile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: args.userId,
|
teamUuid: args.teamUuid,
|
||||||
workflowStatus: {
|
workflowStatus: {
|
||||||
in: ['active', 'approved', 'demo'],
|
in: ['active', 'approved', 'demo'],
|
||||||
},
|
},
|
||||||
@@ -122,13 +124,15 @@ async function pricingContextForUser(_: unknown, args: { userId: string }) {
|
|||||||
return mapPricingContext(profile)
|
return mapPricingContext(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureDemoPricingContextForUser(
|
async function ensureDemoPricingContextForTeam(
|
||||||
_: unknown,
|
_: unknown,
|
||||||
args: { userId: string; countryCode: string },
|
args: { teamUuid: string; userId: string; countryCode: string },
|
||||||
) {
|
) {
|
||||||
|
const teamUuid = args.teamUuid.trim()
|
||||||
|
if (teamUuid.length === 0) throw new Error('Demo pricing team is required')
|
||||||
const existing = await prisma.kYCProfile.findFirst({
|
const existing = await prisma.kYCProfile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: args.userId,
|
teamUuid,
|
||||||
workflowStatus: {
|
workflowStatus: {
|
||||||
in: ['active', 'approved', 'demo'],
|
in: ['active', 'approved', 'demo'],
|
||||||
},
|
},
|
||||||
@@ -140,18 +144,20 @@ async function ensureDemoPricingContextForUser(
|
|||||||
if (existing !== null) return mapPricingContext(existing)
|
if (existing !== null) return mapPricingContext(existing)
|
||||||
|
|
||||||
const countryCode = normalizedCountryCode(args.countryCode)
|
const countryCode = normalizedCountryCode(args.countryCode)
|
||||||
const profileUuid = demoProfileUuid(args.userId)
|
const profileUuid = demoProfileUuid(teamUuid)
|
||||||
const profile = await prisma.kYCProfile.upsert({
|
const profile = await prisma.kYCProfile.upsert({
|
||||||
where: { uuid: profileUuid },
|
where: { uuid: profileUuid },
|
||||||
create: {
|
create: {
|
||||||
uuid: profileUuid,
|
uuid: profileUuid,
|
||||||
userId: args.userId,
|
userId: args.userId,
|
||||||
|
teamUuid,
|
||||||
teamName: 'Demo workspace',
|
teamName: 'Demo workspace',
|
||||||
countryCode,
|
countryCode,
|
||||||
workflowStatus: 'demo',
|
workflowStatus: 'demo',
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
userId: args.userId,
|
userId: args.userId,
|
||||||
|
teamUuid,
|
||||||
teamName: 'Demo workspace',
|
teamName: 'Demo workspace',
|
||||||
countryCode,
|
countryCode,
|
||||||
workflowStatus: 'demo',
|
workflowStatus: 'demo',
|
||||||
@@ -163,11 +169,11 @@ async function ensureDemoPricingContextForUser(
|
|||||||
export const m2mResolvers = {
|
export const m2mResolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
health: () => 'ok',
|
health: () => 'ok',
|
||||||
pricingContextForUser,
|
pricingContextForTeam,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
createKycProfile: createProfile,
|
createKycProfile: createProfile,
|
||||||
createKycMonitoring: createProfile,
|
createKycMonitoring: createProfile,
|
||||||
ensureDemoPricingContextForUser,
|
ensureDemoPricingContextForTeam,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const managerTypeDefs = `#graphql
|
|||||||
id: Int!
|
id: Int!
|
||||||
uuid: String!
|
uuid: String!
|
||||||
userId: String!
|
userId: String!
|
||||||
|
teamUuid: String
|
||||||
teamName: String
|
teamName: String
|
||||||
countryCode: String
|
countryCode: String
|
||||||
workflowStatus: String!
|
workflowStatus: String!
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const userTypeDefs = `#graphql
|
|||||||
id: Int!
|
id: Int!
|
||||||
uuid: String!
|
uuid: String!
|
||||||
userId: String!
|
userId: String!
|
||||||
|
teamUuid: String
|
||||||
teamName: String
|
teamName: String
|
||||||
countryCode: String
|
countryCode: String
|
||||||
workflowStatus: String!
|
workflowStatus: String!
|
||||||
@@ -21,6 +22,7 @@ export const userTypeDefs = `#graphql
|
|||||||
}
|
}
|
||||||
|
|
||||||
input KYCApplicationRussiaInput {
|
input KYCApplicationRussiaInput {
|
||||||
|
teamUuid: String!
|
||||||
companyName: String!
|
companyName: String!
|
||||||
companyFullName: String!
|
companyFullName: String!
|
||||||
inn: String!
|
inn: String!
|
||||||
@@ -81,6 +83,7 @@ async function getCountryData(app: { objectId: number | null }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RussiaInput {
|
interface RussiaInput {
|
||||||
|
teamUuid: string
|
||||||
companyName: string
|
companyName: string
|
||||||
companyFullName: string
|
companyFullName: string
|
||||||
inn: string
|
inn: string
|
||||||
@@ -97,6 +100,8 @@ interface RussiaInput {
|
|||||||
|
|
||||||
async function createApplicationRussia(_: unknown, args: { input: RussiaInput }, ctx: AuthContext) {
|
async function createApplicationRussia(_: unknown, args: { input: RussiaInput }, ctx: AuthContext) {
|
||||||
if (!ctx.userId) throw new GraphQLError('Not authenticated')
|
if (!ctx.userId) throw new GraphQLError('Not authenticated')
|
||||||
|
const teamUuid = args.input.teamUuid.trim()
|
||||||
|
if (teamUuid.length === 0) throw new GraphQLError('Team is required')
|
||||||
|
|
||||||
const details = await prisma.kYCDetailsRussia.create({
|
const details = await prisma.kYCDetailsRussia.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -117,6 +122,7 @@ async function createApplicationRussia(_: unknown, args: { input: RussiaInput },
|
|||||||
const app = await prisma.kYCApplication.create({
|
const app = await prisma.kYCApplication.create({
|
||||||
data: {
|
data: {
|
||||||
userId: ctx.userId,
|
userId: ctx.userId,
|
||||||
|
teamUuid,
|
||||||
teamName: args.input.companyName,
|
teamName: args.input.companyName,
|
||||||
countryCode: 'RU',
|
countryCode: 'RU',
|
||||||
contactPerson: args.input.contactPerson,
|
contactPerson: args.input.contactPerson,
|
||||||
|
|||||||
Reference in New Issue
Block a user