Files
FEDEO/src/routes/tenant.ts
2025-12-08 15:09:15 +01:00

245 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { FastifyInstance } from "fastify"
import jwt from "jsonwebtoken"
import { secrets } from "../utils/secrets"
import {
authTenantUsers,
authUsers,
authProfiles,
tenants
} from "../../db/schema"
import {and, eq, inArray} from "drizzle-orm"
export default async function tenantRoutes(server: FastifyInstance) {
// -------------------------------------------------------------
// GET CURRENT TENANT
// -------------------------------------------------------------
server.get("/tenant", async (req) => {
if (req.tenant) {
return {
message: `Hallo vom Tenant ${req.tenant?.name}`,
tenant_id: req.tenant?.id,
}
}
return {
message: "Server ist im MultiTenant-Modus es werden alle verfügbaren Tenants geladen."
}
})
// -------------------------------------------------------------
// SWITCH TENANT
// -------------------------------------------------------------
server.post("/tenant/switch", async (req, reply) => {
try {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" })
}
const { tenant_id } = req.body as { tenant_id: string }
if (!tenant_id) return reply.code(400).send({ error: "tenant_id required" })
// prüfen ob der User zu diesem Tenant gehört
const membership = await server.db
.select()
.from(authTenantUsers)
.where(and(
eq(authTenantUsers.user_id, req.user.user_id),
eq(authTenantUsers.tenant_id, Number(tenant_id))
))
if (!membership.length) {
return reply.code(403).send({ error: "Not a member of this tenant" })
}
// JWT neu erzeugen
const token = jwt.sign(
{
user_id: req.user.user_id,
email: req.user.email,
tenant_id,
},
secrets.JWT_SECRET!,
{ expiresIn: "6h" }
)
reply.setCookie("token", token, {
path: "/",
httpOnly: true,
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 3,
})
return { token }
} catch (err) {
console.error("TENANT SWITCH ERROR:", err)
return reply.code(500).send({ error: "Internal Server Error" })
}
})
// -------------------------------------------------------------
// TENANT USERS (auth_users + auth_profiles)
// -------------------------------------------------------------
server.get("/tenant/users", async (req, reply) => {
try {
const authUser = req.user
if (!authUser) return reply.code(401).send({ error: "Unauthorized" })
const tenantId = authUser.tenant_id
// 1) auth_tenant_users → user_ids
const tenantUsers = await server.db
.select()
.from(authTenantUsers)
.where(eq(authTenantUsers.tenant_id, tenantId))
const userIds = tenantUsers.map(u => u.user_id)
if (!userIds.length) {
return { tenant_id: tenantId, users: [] }
}
// 2) auth_users laden
const users = await server.db
.select()
.from(authUsers)
.where(inArray(authUsers.id, userIds))
// 3) auth_profiles pro Tenant laden
const profiles = await server.db
.select()
.from(authProfiles)
.where(
and(
eq(authProfiles.tenant_id, tenantId),
inArray(authProfiles.user_id, userIds)
))
const combined = users.map(u => {
const profile = profiles.find(p => p.user_id === u.id)
return {
id: u.id,
email: u.email,
profile,
full_name: profile?.full_name ?? null
}
})
return { tenant_id: tenantId, users: combined }
} catch (err) {
console.error("/tenant/users ERROR:", err)
return reply.code(500).send({ error: "Internal Server Error" })
}
})
// -------------------------------------------------------------
// TENANT PROFILES
// -------------------------------------------------------------
server.get("/tenant/profiles", async (req, reply) => {
try {
const tenantId = req.user?.tenant_id
if (!tenantId) return reply.code(401).send({ error: "Unauthorized" })
const data = await server.db
.select()
.from(authProfiles)
.where(eq(authProfiles.tenant_id, tenantId))
return { data }
} catch (err) {
console.error("/tenant/profiles ERROR:", err)
return reply.code(500).send({ error: "Internal Server Error" })
}
})
// -------------------------------------------------------------
// UPDATE NUMBER RANGE
// -------------------------------------------------------------
server.put("/tenant/numberrange/:numberrange", async (req, reply) => {
try {
const user = req.user
if (!user) return reply.code(401).send({ error: "Unauthorized" })
const { numberrange } = req.params as { numberrange: string }
const { numberRange } = req.body as { numberRange: any }
if (!numberRange) {
return reply.code(400).send({ error: "numberRange required" })
}
const tenantId = Number(user.tenant_id)
const currentTenantRows = await server.db
.select()
.from(tenants)
.where(eq(tenants.id, tenantId))
const current = currentTenantRows[0]
if (!current) return reply.code(404).send({ error: "Tenant not found" })
const updatedRanges = {
//@ts-ignore
...current.numberRanges,
[numberrange]: numberRange
}
const updated = await server.db
.update(tenants)
.set({ numberRanges: updatedRanges })
.where(eq(tenants.id, tenantId))
.returning()
return updated[0]
} catch (err) {
console.error("/tenant/numberrange ERROR:", err)
return reply.code(500).send({ error: "Internal Server Error" })
}
})
// -------------------------------------------------------------
// UPDATE TENANT OTHER FIELDS
// -------------------------------------------------------------
server.put("/tenant/other/:id", async (req, reply) => {
try {
const user = req.user
if (!user) return reply.code(401).send({ error: "Unauthorized" })
const { id } = req.params as { id: string }
const { data } = req.body as { data: any }
if (!data) return reply.code(400).send({ error: "data required" })
const updated = await server.db
.update(tenants)
.set(data)
.where(eq(tenants.id, Number(user.tenant_id)))
.returning()
return updated[0]
} catch (err) {
console.error("/tenant/other ERROR:", err)
return reply.code(500).send({ error: "Internal Server Error" })
}
})
}