@igstack/app-catalog-backend-core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,169 +0,0 @@
1
- import { z } from 'zod'
2
- import { Prisma } from '../../db/prisma'
3
- import { getDbClient } from '../../db'
4
- import { adminProcedure, publicProcedure, router } from '../../server/trpcSetup'
5
- import type {
6
- ApprovalMethod,
7
- ApprovalMethodConfig,
8
- CustomConfig,
9
- PersonTeamConfig,
10
- ServiceConfig,
11
- } from '../../types'
12
- import { generateSlugFromDisplayName } from './slugUtils'
13
-
14
- // Zod schemas
15
- const ReachOutContactSchema = z.object({
16
- displayName: z.string(),
17
- contact: z.string(),
18
- })
19
-
20
- const ServiceConfigSchema = z.object({
21
- url: z.url().optional(),
22
- icon: z.string().optional(),
23
- })
24
-
25
- const PersonTeamConfigSchema = z.object({
26
- reachOutContacts: z.array(ReachOutContactSchema).optional(),
27
- })
28
-
29
- const CustomConfigSchema = z.object({})
30
-
31
- const ApprovalMethodConfigSchema = z.union([
32
- ServiceConfigSchema,
33
- PersonTeamConfigSchema,
34
- CustomConfigSchema,
35
- ])
36
-
37
- const CreateApprovalMethodSchema = z.object({
38
- type: z.enum(['service', 'personTeam', 'custom']),
39
- displayName: z.string().min(1),
40
- config: ApprovalMethodConfigSchema.optional(),
41
- })
42
-
43
- const UpdateApprovalMethodSchema = z.object({
44
- slug: z.string(),
45
- type: z.enum(['service', 'personTeam', 'custom']).optional(),
46
- displayName: z.string().min(1).optional(),
47
- config: ApprovalMethodConfigSchema.optional(),
48
- })
49
-
50
- /**
51
- * Convert Prisma DbApprovalMethod to our ApprovalMethod type.
52
- * This ensures tRPC infers proper types for frontend consumers.
53
- */
54
- function toApprovalMethod(db: {
55
- slug: string
56
- type: ApprovalMethod['type']
57
- displayName: string
58
- config: ApprovalMethodConfig | null
59
- createdAt: Date
60
- updatedAt: Date
61
- }): ApprovalMethod {
62
- // Handle discriminated union by explicitly narrowing based on type
63
- const baseFields = {
64
- slug: db.slug,
65
- displayName: db.displayName,
66
- createdAt: db.createdAt,
67
- updatedAt: db.updatedAt,
68
- }
69
-
70
- // Provide default empty config if null, as ApprovalMethod discriminated union requires config
71
- const config = db.config ?? {}
72
-
73
- switch (db.type) {
74
- case 'service':
75
- return { ...baseFields, type: 'service', config: config as ServiceConfig }
76
- case 'personTeam':
77
- return {
78
- ...baseFields,
79
- type: 'personTeam',
80
- config: config as PersonTeamConfig,
81
- }
82
- case 'custom':
83
- return { ...baseFields, type: 'custom', config: config as CustomConfig }
84
- }
85
- }
86
-
87
- export function createApprovalMethodRouter() {
88
- return router({
89
- // Public: list for selection in app admin
90
- list: publicProcedure.query(async (): Promise<Array<ApprovalMethod>> => {
91
- const prisma = getDbClient()
92
- const results = await prisma.dbApprovalMethod.findMany({
93
- orderBy: { displayName: 'asc' },
94
- })
95
- return results.map(toApprovalMethod)
96
- }),
97
-
98
- // Public: get by ID
99
- getById: publicProcedure
100
- .input(z.object({ slug: z.string() }))
101
- .query(async ({ input }): Promise<ApprovalMethod | null> => {
102
- const prisma = getDbClient()
103
- const result = await prisma.dbApprovalMethod.findUnique({
104
- where: { slug: input.slug },
105
- })
106
- return result ? toApprovalMethod(result) : null
107
- }),
108
-
109
- // Admin: create
110
- create: adminProcedure
111
- .input(CreateApprovalMethodSchema)
112
- .mutation(async ({ input }): Promise<ApprovalMethod> => {
113
- const prisma = getDbClient()
114
- const result = await prisma.dbApprovalMethod.create({
115
- data: {
116
- slug: generateSlugFromDisplayName(input.displayName),
117
- type: input.type,
118
- displayName: input.displayName,
119
- config: input.config ?? Prisma.JsonNull,
120
- },
121
- })
122
- return toApprovalMethod(result)
123
- }),
124
-
125
- // Admin: update
126
- update: adminProcedure
127
- .input(UpdateApprovalMethodSchema)
128
- .mutation(async ({ input }): Promise<ApprovalMethod> => {
129
- const prisma = getDbClient()
130
- const { slug, ...updateData } = input
131
- const result = await prisma.dbApprovalMethod.update({
132
- where: { slug },
133
- data: {
134
- ...(updateData.type !== undefined && { type: updateData.type }),
135
- ...(updateData.displayName !== undefined && {
136
- displayName: updateData.displayName,
137
- }),
138
- ...(updateData.config !== undefined && {
139
- config: updateData.config ?? Prisma.JsonNull,
140
- }),
141
- },
142
- })
143
- return toApprovalMethod(result)
144
- }),
145
-
146
- // Admin: delete
147
- delete: adminProcedure
148
- .input(z.object({ slug: z.string() }))
149
- .mutation(async ({ input }): Promise<ApprovalMethod> => {
150
- const prisma = getDbClient()
151
- const result = await prisma.dbApprovalMethod.delete({
152
- where: { slug: input.slug },
153
- })
154
- return toApprovalMethod(result)
155
- }),
156
-
157
- // Admin: search by type
158
- listByType: publicProcedure
159
- .input(z.object({ type: z.enum(['service', 'personTeam', 'custom']) }))
160
- .query(async ({ input }): Promise<Array<ApprovalMethod>> => {
161
- const prisma = getDbClient()
162
- const results = await prisma.dbApprovalMethod.findMany({
163
- where: { type: input.type },
164
- orderBy: { displayName: 'asc' },
165
- })
166
- return results.map(toApprovalMethod)
167
- }),
168
- })
169
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Generates a URL-friendly slug from a display name.
3
- * Converts to lowercase and replaces non-alphanumeric characters with hyphens.
4
- *
5
- * @param displayName - The display name to convert
6
- * @returns A slug suitable for use as a primary key
7
- *
8
- * @example
9
- * generateSlugFromDisplayName("My Service") // "my-service"
10
- * generateSlugFromDisplayName("John's Team") // "john-s-team"
11
- */
12
- export function generateSlugFromDisplayName(displayName: string): string {
13
- return displayName
14
- .toLowerCase()
15
- .replace(/[^a-z0-9]+/g, '-')
16
- .replace(/^-+|-+$/g, '')
17
- }
@@ -1,38 +0,0 @@
1
- import type { PrismaClient as CorePrismaClient } from '../../db/prisma'
2
-
3
- export interface ApprovalMethodSyncInput {
4
- slug: string
5
- type: 'service' | 'personTeam' | 'custom'
6
- displayName: string
7
- }
8
-
9
- /**
10
- * Syncs approval methods to the database using upsert logic based on type + displayName.
11
- *
12
- * @param prisma - The PrismaClient instance from the backend-core database
13
- * @param methods - Array of approval methods to sync
14
- */
15
- export async function syncApprovalMethods(
16
- prisma: CorePrismaClient,
17
- methods: Array<ApprovalMethodSyncInput>,
18
- ): Promise<void> {
19
- // Use transaction for atomicity
20
- await prisma.$transaction(
21
- methods.map((method) =>
22
- prisma.dbApprovalMethod.upsert({
23
- where: {
24
- slug: method.slug,
25
- },
26
- update: {
27
- displayName: method.displayName,
28
- type: method.type,
29
- },
30
- create: {
31
- slug: method.slug,
32
- type: method.type,
33
- displayName: method.displayName,
34
- },
35
- }),
36
- ),
37
- )
38
- }