This commit is contained in:
2025-09-12 18:29:13 +02:00
parent dc385b8422
commit c98394b5bf
17 changed files with 1786 additions and 9 deletions

View File

@@ -1,8 +1,8 @@
import { FastifyInstance } from "fastify";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { generateRandomPassword, hashPassword } from "../utils/password"
import { sendMail } from "../utils/mailer"
import { generateRandomPassword, hashPassword } from "../../utils/password"
import { sendMail } from "../../utils/mailer"
export default async function authRoutes(server: FastifyInstance) {
// Registrierung

View File

@@ -25,7 +25,7 @@ export default async function meRoutes(server: FastifyInstance) {
// 2. Tenants laden (alle Tenants des Users)
const { data: tenantLinks, error: tenantLinksError } = await server.supabase
.from("auth_users")
.select(`*, tenants!auth_tenant_users ( id, name, locked )`)
.select(`*, tenants!auth_tenant_users ( id, name,short, locked, extraModules, businessInfo, numberRanges, dokuboxkey )`)
.eq("id", authUser.user_id)
.single();

67
src/routes/auth/user.ts Normal file
View File

@@ -0,0 +1,67 @@
import { FastifyInstance } from "fastify";
export default async function userRoutes(server: FastifyInstance) {
//TODO: PERMISSIONS Rückmeldung beschränken
server.get("/user/:id", async (req, reply) => {
const authUser = req.user // kommt aus JWT (user_id + tenant_id)
const {id} = req.params
if (!authUser) {
return reply.code(401).send({ error: "Unauthorized" })
}
// 1. User laden
const { data: user, error: userError } = await server.supabase
.from("auth_users")
.select("id, email, created_at, must_change_password")
.eq("id", id)
.single()
if (userError || !user) {
return reply.code(401).send({ error: "User not found" })
}
// 2. Tenants laden (alle Tenants des Users)
/*const { data: tenantLinks, error: tenantLinksError } = await server.supabase
.from("auth_users")
.select(`*, tenants!auth_tenant_users ( id, name, locked )`)
.eq("id", authUser.user_id)
.single();
if (tenantLinksError) {
console.log(tenantLinksError)
return reply.code(401).send({ error: "Tenant Error" })
}
const tenants = tenantLinks?.tenants*/
// 3. Aktiven Tenant bestimmen
const activeTenant = authUser.tenant_id /*|| tenants[0].id*/
// 4. Profil für den aktiven Tenant laden
let profile = null
if (activeTenant) {
const { data: profileData } = await server.supabase
.from("auth_profiles")
.select("*")
.eq("user_id", id)
.eq("tenant_id", activeTenant)
.single()
profile = profileData
}
// 5. Permissions laden (über Funktion)
// 6. Response zurückgeben
return {
user,
profile,
}
})
}

72
src/routes/banking.ts Normal file
View File

@@ -0,0 +1,72 @@
import { FastifyInstance } from "fastify";
import jwt from "jsonwebtoken";
import {insertHistoryItem} from "../utils/history";
export default async function bankingRoutes(server: FastifyInstance) {
//Create Banking Statement
server.post("/banking/statements", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const body = req.body as { data: string };
console.log(body);
const {data,error} = await server.supabase.from("statementallocations").insert({
...body.data,
tenant: req.user.tenant_id,
}).select()
await insertHistoryItem(server,{
entity: "bankstatements",
entityId: data.id,
action: "created",
created_by: req.user.user_id,
tenant_id: req.user.tenant_id,
oldVal: null,
newVal: data,
text: `Buchung erstellt`,
});
if(data && !error){
return reply.send(data)
}
});
//Delete Banking Statement
server.delete("/banking/statements/:id", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { id } = req.params as { id?: string }
const {data} = await server.supabase.from("statementallocations").select().eq("id",id).single()
const {error} = await server.supabase.from("statementallocations").delete().eq("id",id)
if(!error){
await insertHistoryItem(server,{
entity: "bankstatements",
entityId: id,
action: "deleted",
created_by: req.user.user_id,
tenant_id: req.user.tenant_id,
oldVal: data,
newVal: null,
text: `Buchung gelöscht`,
});
return reply.send({success:true})
} else {
return reply.code(500).send({error:"Fehler beim löschen"})
}
})
}

30
src/routes/emailAsUser.ts Normal file
View File

@@ -0,0 +1,30 @@
import { FastifyInstance } from "fastify";
import {createInvoicePDF} from "../utils/pdf";
import {useNextNumberRangeNumber} from "../utils/functions";
import {sendMailAsUser} from "../utils/emailengine";
import {subtle} from "node:crypto";
export default async function emailAsUserRoutes(server: FastifyInstance) {
server.post("/emailasuser/send", async (req, reply) => {
const body = req.body as {
to: string
cc?: string
bcc?: string
subject?: string
text?: string
html?: string
attachments?: any,
account: string
}
try {
reply.send(await sendMailAsUser(body.to,body.subject,body.html,body.text,body.account,body.cc,body.bcc,body.attachments))
} catch (err) {
console.log(err)
reply.code(500).send({ error: "Failed to send E-Mail as User" })
}
})
}

105
src/routes/exports.ts Normal file
View File

@@ -0,0 +1,105 @@
import { FastifyInstance } from "fastify";
import jwt from "jsonwebtoken";
import {insertHistoryItem} from "../utils/history";
import {buildExportZip} from "../utils/export/datev";
import {s3} from "../utils/s3";
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
import dayjs from "dayjs";
import {randomUUID} from "node:crypto";
const createExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => {
console.log(startDate,endDate,beraternr,mandantennr)
// 1) ZIP erzeugen
const buffer = await buildExportZip(server,req.user.tenant_id, startDate, endDate, beraternr, mandantennr)
console.log("ZIP created")
// 2) Dateiname & Key festlegen
const fileKey = `${req.user.tenant_id}/exports/Export_${dayjs(startDate).format("YYYY-MM-DD")}_${dayjs(endDate).format("YYYY-MM-DD")}_${randomUUID()}.zip`
console.log(fileKey)
// 3) In S3 hochladen
await s3.send(
new PutObjectCommand({
Bucket: process.env.S3_BUCKET || "FEDEO",
Key: fileKey,
Body: buffer,
ContentType: "application/zip",
})
)
// 4) Presigned URL erzeugen (24h gültig)
const url = await getSignedUrl(
s3,
new GetObjectCommand({
Bucket: process.env.S3_BUCKET || "FEDEO",
Key: fileKey,
}),
{ expiresIn: 60 * 60 * 24 }
)
console.log(url)
// 5) In Supabase-DB speichern
const { data, error } = await server.supabase
.from("exports")
.insert([
{
tenant_id: req.user.tenant_id,
start_date: startDate,
end_date: endDate,
valid_until: dayjs().add(24,"hours").toISOString(),
file_path: fileKey,
url: url,
created_at: new Date().toISOString(),
},
])
.select()
.single()
console.log(data)
console.log(error)
}
export default async function exportRoutes(server: FastifyInstance) {
//Export DATEV
server.post("/exports/datev", async (req, reply) => {
const { start_date, end_date, beraternr, mandantennr } = req.body as {
start_date: string
end_date: string
beraternr: string
mandantennr: string
}
reply.send({success:true})
setImmediate(async () => {
try {
await createExport(server,req,start_date,end_date,beraternr,mandantennr)
console.log("Job done ✅")
} catch (err) {
console.error("Job failed ❌", err)
}
})
})
//List Exports Available for Download
server.get("/exports", async (req,reply) => {
const {data,error} = await server.supabase.from("exports").select().eq("tenant_id",req.user.tenant_id)
console.log(data,error)
reply.send(data)
})
}

View File

@@ -43,7 +43,6 @@ export default async function fileRoutes(server: FastifyInstance) {
if (!data.file) return reply.code(400).send({ error: "No file uploaded" })
console.log("ENDE")
const {data:createdFileData,error:createdFileError} = await server.supabase
.from("files")
@@ -207,7 +206,7 @@ export default async function fileRoutes(server: FastifyInstance) {
}
try {
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*").in("id", ids)
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*, createddocument(*, customer(*))").in("id", ids)

42
src/routes/functions.ts Normal file
View File

@@ -0,0 +1,42 @@
import { FastifyInstance } from "fastify";
import {createInvoicePDF} from "../utils/pdf";
import {useNextNumberRangeNumber} from "../utils/functions";
export default async function functionRoutes(server: FastifyInstance) {
server.post("/functions/createinvoicepdf", async (req, reply) => {
const body = req.body as {
invoiceData: any
backgroundPath?: string
}
try {
const pdf = await createInvoicePDF(
server,
"base64",
body.invoiceData,
body.backgroundPath
)
reply.send(pdf) // Fastify wandelt automatisch in JSON
} catch (err) {
console.log(err)
reply.code(500).send({ error: "Failed to create PDF" })
}
})
server.get(
"/functions/usenextnumber/:numberrange",
async (req, reply) => {
const { numberrange } = req.params as { numberrange: string };
const tenant = (req as any).user.tenant_id
try {
const result = await useNextNumberRangeNumber(server,tenant, numberrange)
reply.send(result) // JSON automatisch
} catch (err) {
req.log.error(err)
reply.code(500).send({ error: "Failed to generate next number" })
}
}
)
}

View File

@@ -60,5 +60,101 @@ export default async function routes(server: FastifyInstance) {
return { token };
});
server.get("/tenant/users", async (req, reply) => {
const { tenant_id } = req.params as { tenant_id: string };
const authUser = req.user // kommt aus JWT (user_id + tenant_id)
if (!authUser) {
return reply.code(401).send({ error: "Unauthorized" })
}
const { data, error } = await server.supabase
.from("auth_tenant_users")
.select(`
user_id,
auth_users!tenantusers_user_id_fkey ( id, email, created_at, auth_profiles(*))`)
.eq("tenant_id", authUser.tenant_id);
if (error) {
console.log(error);
return reply.code(400).send({ error: error.message });
}
let correctedData = data.map(i => {
return {
id: i.user_id,
email: i.auth_users.email,
profile: i.auth_users.auth_profiles.find(x => x.tenant_id === authUser.tenant_id),
full_name: i.auth_users.auth_profiles.find(x => x.tenant_id === authUser.tenant_id)?.full_name,
}
})
return { tenant_id, users: correctedData };
});
server.put("/tenant/numberrange/:numberrange", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { numberrange } = req.params as { numberrange?: string }
const body = req.body as { numberRange: object };
console.log(body);
if(!body.numberRange) {
return reply.code(400).send({ error: "numberRange required" });
}
const {data:currentTenantData,error:numberRangesError} = await server.supabase.from("tenants").select().eq("id", req.user.tenant_id).single()
console.log(currentTenantData)
console.log(numberRangesError)
let numberRanges = {
// @ts-ignore
...currentTenantData.numberRanges
}
// @ts-ignore
numberRanges[numberrange] = body.numberRange
console.log(numberRanges)
const {data,error} = await server.supabase
.from("tenants")
.update({numberRanges: numberRanges})
.eq('id',req.user.tenant_id)
.select()
if(data && !error) {
return reply.send(data)
}
});
server.put("/tenant/other/:id", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { id } = req.params as { id?: string }
const body = req.body as { data: object };
console.log(body);
if(!body.data) {
return reply.code(400).send({ error: "data required" });
}
const {data:dataReturn,error} = await server.supabase
.from("tenants")
.update(body.data)
.eq('id',req.user.tenant_id)
.select()
if(dataReturn && !error) {
return reply.send(dataReturn)
}
});
}