Migrate teams backend from Django to Express + Apollo Server + Prisma
All checks were successful
Build Docker Image / build (push) Successful in 2m8s

Replace Django/Graphene stack with TypeScript Express server using Apollo Server v4
with 4 GraphQL endpoints (public/user/team/m2m) and Prisma ORM mapped to existing tables.
This commit is contained in:
Ruslan Bakiev
2026-03-09 09:26:41 +07:00
parent e52f5947a2
commit d9f1a066ce
82 changed files with 5164 additions and 3820 deletions

189
src/schemas/user.ts Normal file
View File

@@ -0,0 +1,189 @@
import { GraphQLError } from 'graphql'
import { prisma } from '../db.js'
import { startTeamCreated } from '../services/temporal.js'
import type { AuthContext } from '../auth.js'
export const userTypeDefs = `#graphql
type UserTeam {
id: String!
name: String!
teamType: String
logtoOrgId: String
createdAt: String
}
type User {
id: String
firstName: String
lastName: String
phone: String
avatarId: String
activeTeamId: String
activeTeam: UserTeam
teams: [UserTeam]
}
type TeamMemberInfo {
uuid: String!
role: String!
joinedAt: String
}
type TeamInvitationInfo {
uuid: String!
email: String!
role: String!
status: String!
invitedBy: String
expiresAt: String
createdAt: String
}
type TeamWithMembers {
uuid: String!
name: String!
members: [TeamMemberInfo]
invitations: [TeamInvitationInfo]
}
input CreateTeamInput {
name: String!
teamType: String
}
input UpdateUserInput {
firstName: String
lastName: String
phone: String
avatarId: String
}
type CreateTeamResult {
team: UserTeam
success: Boolean!
}
type UpdateUserResult {
user: User
success: Boolean!
}
type SwitchTeamResult {
success: Boolean!
}
type Query {
me: User
getTeam(teamId: String!): TeamWithMembers
}
type Mutation {
createTeam(input: CreateTeamInput!): CreateTeamResult
updateUser(userId: String!, input: UpdateUserInput!): UpdateUserResult
switchTeam(teamId: String!): SwitchTeamResult
}
`
async function getOrCreateProfile(logtoId: string) {
let profile = await prisma.userProfile.findUnique({ where: { logtoId: logtoId }, include: { user: true, activeTeam: true } })
if (!profile) {
const user = await prisma.user.create({ data: { username: logtoId, firstName: '', lastName: '' } })
profile = await prisma.userProfile.create({
data: { userId: user.id, logtoId: logtoId },
include: { user: true, activeTeam: true },
})
}
return profile
}
export const userResolvers = {
Query: {
me: async (_: unknown, __: unknown, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError('Not authenticated')
const profile = await getOrCreateProfile(ctx.userId)
const memberships = await prisma.teamMember.findMany({ where: { userId: profile.userId }, include: { team: true } })
return {
id: ctx.userId,
firstName: profile.user.firstName,
lastName: profile.user.lastName,
phone: profile.phone,
avatarId: profile.avatarId,
activeTeamId: profile.activeTeam?.uuid ?? null,
activeTeam: profile.activeTeam ? { id: profile.activeTeam.uuid, name: profile.activeTeam.name, teamType: profile.activeTeam.teamType, logtoOrgId: profile.activeTeam.logtoOrgId, createdAt: profile.activeTeam.createdAt.toISOString() } : null,
teams: memberships.map(m => ({ id: m.team.uuid, name: m.team.name, teamType: m.team.teamType, logtoOrgId: m.team.logtoOrgId, createdAt: m.team.createdAt.toISOString() })),
}
},
getTeam: async (_: unknown, args: { teamId: string }, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError('Not authenticated')
const team = await prisma.team.findUnique({
where: { uuid: args.teamId },
include: {
members: { include: { user: true } },
invitations: true,
},
})
if (!team) return null
return {
uuid: team.uuid,
name: team.name,
members: team.members.map(m => ({ uuid: m.uuid, role: m.role, joinedAt: m.joinedAt.toISOString() })),
invitations: team.invitations.map(i => ({
uuid: i.uuid, email: i.email, role: i.role, status: i.status,
invitedBy: i.invitedBy, expiresAt: i.expiresAt?.toISOString() ?? null,
createdAt: i.createdAt.toISOString(),
})),
}
},
},
Mutation: {
createTeam: async (_: unknown, args: { input: { name: string; teamType?: string } }, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError('Not authenticated')
const profile = await getOrCreateProfile(ctx.userId)
const team = await prisma.team.create({
data: { name: args.input.name, teamType: args.input.teamType || 'BUYER', ownerId: profile.userId },
})
await prisma.teamMember.create({ data: { teamId: team.id, userId: profile.userId, role: 'OWNER' } })
await prisma.userProfile.update({ where: { id: profile.id }, data: { activeTeamId: team.id } })
try {
await startTeamCreated(team.uuid, team.name, ctx.userId, team.teamType)
} catch (e) {
console.error('Failed to start team_created workflow:', e)
}
return {
team: { id: team.uuid, name: team.name, teamType: team.teamType, logtoOrgId: team.logtoOrgId, createdAt: team.createdAt.toISOString() },
success: true,
}
},
updateUser: async (_: unknown, args: { userId: string; input: { firstName?: string; lastName?: string; phone?: string; avatarId?: string } }, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError('Not authenticated')
const profile = await getOrCreateProfile(ctx.userId)
const userUpdate: Record<string, unknown> = {}
if (args.input.firstName !== undefined) userUpdate.firstName = args.input.firstName
if (args.input.lastName !== undefined) userUpdate.lastName = args.input.lastName
if (Object.keys(userUpdate).length > 0) {
await prisma.user.update({ where: { id: profile.userId }, data: userUpdate })
}
const profileUpdate: Record<string, unknown> = {}
if (args.input.phone !== undefined) profileUpdate.phone = args.input.phone
if (args.input.avatarId !== undefined) profileUpdate.avatarId = args.input.avatarId
if (Object.keys(profileUpdate).length > 0) {
await prisma.userProfile.update({ where: { id: profile.id }, data: profileUpdate })
}
return { user: { id: ctx.userId }, success: true }
},
switchTeam: async (_: unknown, args: { teamId: string }, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError('Not authenticated')
const profile = await getOrCreateProfile(ctx.userId)
const team = await prisma.team.findUnique({ where: { uuid: args.teamId } })
if (!team) throw new GraphQLError('Team not found')
await prisma.userProfile.update({ where: { id: profile.id }, data: { activeTeamId: team.id } })
return { success: true }
},
},
}