Skip to content

Commit 1322a2f

Browse files
pktikkaniclaude
andcommitted
Add Azure deployment config and auto-user creation
- Configure Prisma 7 with driver adapters for migrations - Add binary targets for Azure Linux deployment - Add debug and auth-debug endpoints for troubleshooting - Auto-create users on first Azure AD login - Add .funcignore for pre-built deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d6f492c commit 1322a2f

6 files changed

Lines changed: 113 additions & 27 deletions

File tree

.funcignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Ignore for Azure Functions deployment (pre-built)
2+
.git
3+
.github
4+
.vscode
5+
.idea
6+
*.md
7+
*.log
8+
.env*
9+
local.settings.json
10+
.gitignore
11+
coverage/
12+
src/
13+
tsconfig.json
14+
*.ts
15+
!*.d.ts
16+
deploy.zip

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@
3939
"vitest": "^2.1.8"
4040
},
4141
"engines": {
42-
"node": ">=18.0.0"
42+
"node": ">=22.0.0"
4343
}
4444
}

prisma/prisma.config.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import path from 'node:path';
2-
import type { PrismaConfig } from 'prisma';
1+
import { defineConfig } from 'prisma/config';
32

4-
export default {
3+
export default defineConfig({
54
earlyAccess: true,
6-
schema: path.join(__dirname, 'schema.prisma'),
7-
5+
schema: './schema.prisma',
86
migrate: {
97
adapter: async () => {
108
const { PrismaPg } = await import('@prisma/adapter-pg');
@@ -13,4 +11,7 @@ export default {
1311
return new PrismaPg(pool);
1412
},
1513
},
16-
} satisfies PrismaConfig;
14+
datasource: {
15+
url: process.env.DATABASE_URL!,
16+
},
17+
});

prisma/schema.prisma

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
generator client {
2-
provider = "prisma-client-js"
2+
provider = "prisma-client-js"
3+
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-musl-openssl-3.0.x"]
34
}
45

56
datasource db {

src/functions/trpc.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,75 @@ app.http('health', {
8383
};
8484
},
8585
});
86+
87+
// Debug endpoint - test database connection
88+
app.http('debug', {
89+
methods: ['GET'],
90+
authLevel: 'anonymous',
91+
route: 'debug',
92+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
93+
try {
94+
const { db } = await import('../lib/db/prisma.js');
95+
const userCount = await db.user.count();
96+
return {
97+
jsonBody: { status: 'ok', database: 'connected', users: userCount },
98+
};
99+
} catch (error) {
100+
return {
101+
status: 500,
102+
jsonBody: {
103+
status: 'error',
104+
message: error instanceof Error ? error.message : 'Unknown',
105+
stack: error instanceof Error ? error.stack : undefined,
106+
},
107+
};
108+
}
109+
},
110+
});
111+
112+
// Auth debug endpoint - test token validation
113+
app.http('auth-debug', {
114+
methods: ['GET', 'POST', 'OPTIONS'],
115+
authLevel: 'anonymous',
116+
route: 'auth-debug',
117+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
118+
const jwt = await import('jsonwebtoken');
119+
120+
const authHeader = request.headers.get('Authorization');
121+
if (!authHeader?.startsWith('Bearer ')) {
122+
return {
123+
jsonBody: {
124+
error: 'No Bearer token',
125+
headers: Object.fromEntries(request.headers.entries()),
126+
},
127+
};
128+
}
129+
130+
const token = authHeader.substring(7);
131+
132+
try {
133+
// Decode without verification to see claims
134+
const decoded = jwt.default.decode(token, { complete: true });
135+
136+
return {
137+
jsonBody: {
138+
status: 'token_received',
139+
header: decoded?.header,
140+
payload: decoded?.payload,
141+
envConfig: {
142+
MICROSOFT_TENANT_ID: process.env.MICROSOFT_TENANT_ID,
143+
MICROSOFT_CLIENT_ID: process.env.MICROSOFT_CLIENT_ID,
144+
},
145+
},
146+
};
147+
} catch (error) {
148+
return {
149+
status: 500,
150+
jsonBody: {
151+
status: 'decode_error',
152+
message: error instanceof Error ? error.message : 'Unknown',
153+
},
154+
};
155+
}
156+
},
157+
});

src/trpc/init.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -157,28 +157,24 @@ export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
157157
},
158158
});
159159

160-
// If not found by email, try to find by any associated Microsoft connection
160+
// If not found, auto-create the user on first login
161161
if (!dbUser) {
162-
console.log('[Azure AD] User not found by email, checking Microsoft connections');
163-
const connection = await db.microsoftConnection.findFirst({
164-
where: {
165-
OR: [
166-
{ tenantId: process.env.MICROSOFT_TENANT_ID },
167-
]
162+
console.log('[Azure AD] User not found, creating new user:', azureAdUser.email);
163+
dbUser = await db.user.create({
164+
data: {
165+
auth0Id: azureAdUser.id, // Use Azure AD oid as auth0Id
166+
email: azureAdUser.email,
167+
name: azureAdUser.email.split('@')[0], // Use email prefix as name
168+
role: 'viewer', // Default role
168169
},
169-
include: { user: true },
170-
});
171-
if (connection?.user) {
172-
dbUser = await db.user.findUnique({
173-
where: { id: connection.user.id },
174-
include: {
175-
microsoftConnections: {
176-
where: { isActive: true },
177-
take: 1,
178-
},
170+
include: {
171+
microsoftConnections: {
172+
where: { isActive: true },
173+
take: 1,
179174
},
180-
});
181-
}
175+
},
176+
});
177+
console.log('[Azure AD] New user created:', dbUser.id);
182178
}
183179

184180
if (dbUser) {

0 commit comments

Comments
 (0)