Migrate teams backend from Django to Express + Apollo Server + Prisma
All checks were successful
Build Docker Image / build (push) Successful in 2m8s
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:
189
src/schemas/user.ts
Normal file
189
src/schemas/user.ts
Normal 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 }
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user