mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 17:24:12 +00:00
fix: resolve global typecheck errors
This commit is contained in:
parent
6b4dae8eeb
commit
4386ac5d8d
41 changed files with 797 additions and 223 deletions
|
|
@ -133,7 +133,7 @@ async function seedRules() {
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'community-rules',
|
collection: 'community-rules',
|
||||||
data: rule,
|
data: rule as any,
|
||||||
})
|
})
|
||||||
console.log(`✅ Created: ${rule.name}`)
|
console.log(`✅ Created: ${rule.name}`)
|
||||||
created++
|
created++
|
||||||
|
|
|
||||||
|
|
@ -53,15 +53,19 @@ async function seed() {
|
||||||
// ============================================
|
// ============================================
|
||||||
console.log('\n--- Updating Site Settings ---')
|
console.log('\n--- Updating Site Settings ---')
|
||||||
|
|
||||||
await payload.updateGlobal({
|
const siteSettingsData = {
|
||||||
slug: 'site-settings',
|
tenant: tenantId,
|
||||||
data: {
|
|
||||||
siteName: 'porwoll.de',
|
siteName: 'porwoll.de',
|
||||||
siteTagline: 'Die Webseite von Martin Porwoll',
|
siteTagline: 'Die Webseite von Martin Porwoll',
|
||||||
contact: {
|
contact: {
|
||||||
email: 'info@porwoll.de',
|
email: 'info@porwoll.de',
|
||||||
phone: '0800 80 44 100',
|
phone: '0800 80 44 100',
|
||||||
address: 'Hans-Böckler-Str. 19\n46236 Bottrop',
|
},
|
||||||
|
address: {
|
||||||
|
street: 'Hans-Böckler-Str. 19',
|
||||||
|
zip: '46236',
|
||||||
|
city: 'Bottrop',
|
||||||
|
country: 'Deutschland',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
copyrightText: 'Martin Porwoll',
|
copyrightText: 'Martin Porwoll',
|
||||||
|
|
@ -69,10 +73,29 @@ async function seed() {
|
||||||
},
|
},
|
||||||
seo: {
|
seo: {
|
||||||
defaultMetaTitle: 'porwoll.de | Die Webseite von Martin Porwoll',
|
defaultMetaTitle: 'porwoll.de | Die Webseite von Martin Porwoll',
|
||||||
defaultMetaDescription: 'Martin Porwoll - Whistleblower, Unternehmer, Mensch. Engagiert für Patientenwohl und Transparenz im Gesundheitswesen.',
|
defaultMetaDescription:
|
||||||
},
|
'Martin Porwoll - Whistleblower, Unternehmer, Mensch. Engagiert für Patientenwohl und Transparenz im Gesundheitswesen.',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSiteSettings = await payload.find({
|
||||||
|
collection: 'site-settings',
|
||||||
|
where: { tenant: { equals: tenantId } },
|
||||||
|
limit: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (existingSiteSettings.docs.length > 0) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'site-settings',
|
||||||
|
id: existingSiteSettings.docs[0].id,
|
||||||
|
data: siteSettingsData as any,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'site-settings',
|
||||||
|
data: siteSettingsData as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
console.log('✓ Site Settings updated')
|
console.log('✓ Site Settings updated')
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
@ -891,9 +914,9 @@ async function seed() {
|
||||||
pageIds[p.slug] = p.id as number
|
pageIds[p.slug] = p.id as number
|
||||||
}
|
}
|
||||||
|
|
||||||
await payload.updateGlobal({
|
const navigationData = {
|
||||||
slug: 'navigation',
|
tenant: tenantId,
|
||||||
data: {
|
title: 'Hauptnavigation',
|
||||||
mainMenu: [
|
mainMenu: [
|
||||||
{
|
{
|
||||||
label: 'Whistleblowing',
|
label: 'Whistleblowing',
|
||||||
|
|
@ -926,8 +949,26 @@ async function seed() {
|
||||||
{ label: 'Impressum', linkType: 'page', page: pageIds['impressum'] },
|
{ label: 'Impressum', linkType: 'page', page: pageIds['impressum'] },
|
||||||
{ label: 'Datenschutzerklärung', linkType: 'page', page: pageIds['datenschutz'] },
|
{ label: 'Datenschutzerklärung', linkType: 'page', page: pageIds['datenschutz'] },
|
||||||
],
|
],
|
||||||
},
|
}
|
||||||
|
|
||||||
|
const existingNavigation = await payload.find({
|
||||||
|
collection: 'navigations',
|
||||||
|
where: { tenant: { equals: tenantId } },
|
||||||
|
limit: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (existingNavigation.docs.length > 0) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'navigations',
|
||||||
|
id: existingNavigation.docs[0].id,
|
||||||
|
data: navigationData as any,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'navigations',
|
||||||
|
data: navigationData as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
console.log('✓ Navigation configured')
|
console.log('✓ Navigation configured')
|
||||||
|
|
||||||
console.log('\n========================================')
|
console.log('\n========================================')
|
||||||
|
|
|
||||||
|
|
@ -411,9 +411,9 @@ async function seed() {
|
||||||
|
|
||||||
console.log('Page IDs:', pageIds)
|
console.log('Page IDs:', pageIds)
|
||||||
|
|
||||||
await payload.updateGlobal({
|
const navigationData = {
|
||||||
slug: 'navigation',
|
tenant: tenantId,
|
||||||
data: {
|
title: 'Hauptnavigation',
|
||||||
mainMenu: [
|
mainMenu: [
|
||||||
{
|
{
|
||||||
label: 'Whistleblowing',
|
label: 'Whistleblowing',
|
||||||
|
|
@ -438,8 +438,26 @@ async function seed() {
|
||||||
{ label: 'Impressum', linkType: 'page', page: pageIds['impressum'] },
|
{ label: 'Impressum', linkType: 'page', page: pageIds['impressum'] },
|
||||||
{ label: 'Datenschutzerklärung', linkType: 'page', page: pageIds['datenschutz'] },
|
{ label: 'Datenschutzerklärung', linkType: 'page', page: pageIds['datenschutz'] },
|
||||||
],
|
],
|
||||||
},
|
}
|
||||||
|
|
||||||
|
const existingNavigation = await payload.find({
|
||||||
|
collection: 'navigations',
|
||||||
|
where: { tenant: { equals: tenantId } },
|
||||||
|
limit: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (existingNavigation.docs.length > 0) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'navigations',
|
||||||
|
id: existingNavigation.docs[0].id,
|
||||||
|
data: navigationData as any,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'navigations',
|
||||||
|
data: navigationData as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
console.log('✓ Navigation configured')
|
console.log('✓ Navigation configured')
|
||||||
|
|
||||||
console.log('\n========================================')
|
console.log('\n========================================')
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,10 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!result.user) {
|
||||||
|
throw new Error('Login returned no user')
|
||||||
|
}
|
||||||
|
|
||||||
// Erfolgreicher Login - afterLogin Hook hat bereits geloggt
|
// Erfolgreicher Login - afterLogin Hook hat bereits geloggt
|
||||||
// Setze Cookie für die Session
|
// Setze Cookie für die Session
|
||||||
const response = NextResponse.json({
|
const response = NextResponse.json({
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,11 @@ export async function GET(req: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfen ob die Plattform Facebook oder Instagram ist
|
// Prüfen ob die Plattform Facebook oder Instagram ist
|
||||||
const platform = account.platform as string
|
const platform =
|
||||||
if (!['facebook', 'instagram'].includes(platform)) {
|
typeof account.platform === 'object' && account.platform
|
||||||
|
? account.platform.slug
|
||||||
|
: undefined
|
||||||
|
if (!platform || !['facebook', 'instagram'].includes(platform)) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'This endpoint is only for Facebook and Instagram accounts' },
|
{ error: 'This endpoint is only for Facebook and Instagram accounts' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ export async function GET(request: NextRequest) {
|
||||||
const topicCounts: Record<string, number> = {}
|
const topicCounts: Record<string, number> = {}
|
||||||
channelInteractions.forEach((i) => {
|
channelInteractions.forEach((i) => {
|
||||||
if (i.analysis?.topics && Array.isArray(i.analysis.topics)) {
|
if (i.analysis?.topics && Array.isArray(i.analysis.topics)) {
|
||||||
i.analysis.topics.forEach((t: { topic?: string }) => {
|
i.analysis.topics.forEach((t: { topic?: string | null }) => {
|
||||||
if (t.topic) {
|
if (t.topic) {
|
||||||
topicCounts[t.topic] = (topicCounts[t.topic] || 0) + 1
|
topicCounts[t.topic] = (topicCounts[t.topic] || 0) + 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,14 @@ export async function GET(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const baseWhere: Record<string, unknown> = {
|
const baseWhere: any = {
|
||||||
publishedAt: {
|
publishedAt: {
|
||||||
greater_than_equal: periodStart.toISOString(),
|
greater_than_equal: periodStart.toISOString(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
baseWhere.socialAccount = { equals: parseInt(channelId) }
|
baseWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch current period data
|
// Fetch current period data
|
||||||
|
|
@ -141,7 +141,7 @@ export async function GET(request: NextRequest) {
|
||||||
const escalations = docs.filter((i) => i.flags?.requiresEscalation).length
|
const escalations = docs.filter((i) => i.flags?.requiresEscalation).length
|
||||||
|
|
||||||
// Fetch previous period for comparison
|
// Fetch previous period for comparison
|
||||||
const previousWhere: Record<string, unknown> = {
|
const previousWhere: any = {
|
||||||
publishedAt: {
|
publishedAt: {
|
||||||
greater_than_equal: previousPeriodStart.toISOString(),
|
greater_than_equal: previousPeriodStart.toISOString(),
|
||||||
less_than: previousPeriodEnd.toISOString(),
|
less_than: previousPeriodEnd.toISOString(),
|
||||||
|
|
@ -149,7 +149,7 @@ export async function GET(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
previousWhere.socialAccount = { equals: parseInt(channelId) }
|
previousWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousInteractions = await payload.find({
|
const previousInteractions = await payload.find({
|
||||||
|
|
|
||||||
|
|
@ -68,12 +68,12 @@ export async function GET(request: NextRequest) {
|
||||||
const prevPeriodEnd = periodStart
|
const prevPeriodEnd = periodStart
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const baseWhere: Record<string, unknown> = {
|
const baseWhere: any = {
|
||||||
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
baseWhere.socialAccount = { equals: parseInt(channelId) }
|
baseWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch current period data
|
// Fetch current period data
|
||||||
|
|
@ -100,7 +100,7 @@ export async function GET(request: NextRequest) {
|
||||||
const p90Time = percentile(responseTimes, 90)
|
const p90Time = percentile(responseTimes, 90)
|
||||||
|
|
||||||
// Fetch previous period for trend
|
// Fetch previous period for trend
|
||||||
const prevWhere: Record<string, unknown> = {
|
const prevWhere: any = {
|
||||||
publishedAt: {
|
publishedAt: {
|
||||||
greater_than_equal: prevPeriodStart.toISOString(),
|
greater_than_equal: prevPeriodStart.toISOString(),
|
||||||
less_than: prevPeriodEnd.toISOString(),
|
less_than: prevPeriodEnd.toISOString(),
|
||||||
|
|
@ -108,7 +108,7 @@ export async function GET(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
prevWhere.socialAccount = { equals: parseInt(channelId) }
|
prevWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevInteractions = await payload.find({
|
const prevInteractions = await payload.find({
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,14 @@ export async function GET(request: NextRequest) {
|
||||||
const periodStart = subDays(now, days)
|
const periodStart = subDays(now, days)
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const baseWhere: Record<string, unknown> = {
|
const baseWhere: any = {
|
||||||
publishedAt: {
|
publishedAt: {
|
||||||
greater_than_equal: periodStart.toISOString(),
|
greater_than_equal: periodStart.toISOString(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
baseWhere.socialAccount = { equals: parseInt(channelId) }
|
baseWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch interactions
|
// Fetch interactions
|
||||||
|
|
|
||||||
|
|
@ -54,13 +54,13 @@ export async function GET(request: NextRequest) {
|
||||||
const periodStart = subDays(now, days)
|
const periodStart = subDays(now, days)
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const baseWhere: Record<string, unknown> = {
|
const baseWhere: any = {
|
||||||
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
||||||
linkedContent: { exists: true },
|
linkedContent: { exists: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
baseWhere.socialAccount = { equals: parseInt(channelId) }
|
baseWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch interactions with linked content
|
// Fetch interactions with linked content
|
||||||
|
|
@ -133,7 +133,7 @@ export async function GET(request: NextRequest) {
|
||||||
const topicCounts: Record<string, number> = {}
|
const topicCounts: Record<string, number> = {}
|
||||||
contentInteractions.forEach((i) => {
|
contentInteractions.forEach((i) => {
|
||||||
if (i.analysis?.topics && Array.isArray(i.analysis.topics)) {
|
if (i.analysis?.topics && Array.isArray(i.analysis.topics)) {
|
||||||
i.analysis.topics.forEach((t: { topic?: string }) => {
|
i.analysis.topics.forEach((t: { topic?: string | null }) => {
|
||||||
if (t.topic) {
|
if (t.topic) {
|
||||||
topicCounts[t.topic] = (topicCounts[t.topic] || 0) + 1
|
topicCounts[t.topic] = (topicCounts[t.topic] || 0) + 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,12 @@ export async function GET(request: NextRequest) {
|
||||||
const periodStart = subDays(now, days)
|
const periodStart = subDays(now, days)
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const baseWhere: Record<string, unknown> = {
|
const baseWhere: any = {
|
||||||
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
publishedAt: { greater_than_equal: periodStart.toISOString() },
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelId !== 'all') {
|
if (channelId !== 'all') {
|
||||||
baseWhere.socialAccount = { equals: parseInt(channelId) }
|
baseWhere.socialAccount = { equals: parseInt(channelId, 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch interactions
|
// Fetch interactions
|
||||||
|
|
@ -89,7 +89,7 @@ export async function GET(request: NextRequest) {
|
||||||
|
|
||||||
const sentimentScore = i.analysis?.sentimentScore as number | undefined
|
const sentimentScore = i.analysis?.sentimentScore as number | undefined
|
||||||
|
|
||||||
i.analysis.topics.forEach((t: { topic?: string }) => {
|
i.analysis.topics.forEach((t: { topic?: string | null }) => {
|
||||||
if (!t.topic) return
|
if (!t.topic) return
|
||||||
|
|
||||||
const topic = t.topic.toLowerCase().trim()
|
const topic = t.topic.toLowerCase().trim()
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export async function GET(req: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query
|
// Build query
|
||||||
const where: Record<string, unknown> = {}
|
const where: any = {}
|
||||||
|
|
||||||
if (dateFrom || dateTo) {
|
if (dateFrom || dateTo) {
|
||||||
where.publishedAt = {}
|
where.publishedAt = {}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
const { user } = await payload.auth({ headers: req.headers })
|
const { user } = await payload.auth({ headers: req.headers })
|
||||||
|
|
||||||
if (!user || !(user as Record<string, unknown>).isSuperAdmin) {
|
if (!user || !(user as { isSuperAdmin?: boolean }).isSuperAdmin) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
const limit = parseInt(req.nextUrl.searchParams.get('limit') || '20', 10)
|
const limit = parseInt(req.nextUrl.searchParams.get('limit') || '20', 10)
|
||||||
const severity = req.nextUrl.searchParams.get('severity')
|
const severity = req.nextUrl.searchParams.get('severity')
|
||||||
|
|
||||||
const where: Record<string, unknown> = {}
|
const where: any = {}
|
||||||
if (severity) {
|
if (severity) {
|
||||||
where.severity = { equals: severity }
|
where.severity = { equals: severity }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
const from = req.nextUrl.searchParams.get('from')
|
const from = req.nextUrl.searchParams.get('from')
|
||||||
const to = req.nextUrl.searchParams.get('to')
|
const to = req.nextUrl.searchParams.get('to')
|
||||||
|
|
||||||
const conditions: Record<string, unknown>[] = []
|
const conditions: any[] = []
|
||||||
|
|
||||||
if (level) {
|
if (level) {
|
||||||
conditions.push({ level: { equals: level } })
|
conditions.push({ level: { equals: level } })
|
||||||
|
|
@ -37,7 +37,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
conditions.push({ createdAt: { less_than_equal: to } })
|
conditions.push({ createdAt: { less_than_equal: to } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const where = conditions.length > 0 ? { and: conditions } : {}
|
const where: any = conditions.length > 0 ? { and: conditions } : {}
|
||||||
|
|
||||||
const logs = await payload.find({
|
const logs = await payload.find({
|
||||||
collection: 'monitoring-logs',
|
collection: 'monitoring-logs',
|
||||||
|
|
|
||||||
|
|
@ -40,15 +40,38 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
data: {
|
data: {
|
||||||
redis: resolveSettled(redis, { status: 'offline' }),
|
redis: resolveSettled(redis, {
|
||||||
postgresql: resolveSettled(postgresql, { status: 'offline' }),
|
status: 'offline',
|
||||||
pgbouncer: resolveSettled(pgbouncer, { status: 'offline' }),
|
memoryUsedMB: 0,
|
||||||
smtp: resolveSettled(smtp, { status: 'offline' }),
|
connectedClients: 0,
|
||||||
oauth: resolveSettled(oauth, {
|
opsPerSec: 0,
|
||||||
metaOAuth: { status: 'error' },
|
}),
|
||||||
youtubeOAuth: { status: 'error' },
|
postgresql: resolveSettled(postgresql, {
|
||||||
|
status: 'offline',
|
||||||
|
connections: 0,
|
||||||
|
maxConnections: 0,
|
||||||
|
latencyMs: -1,
|
||||||
|
}),
|
||||||
|
pgbouncer: resolveSettled(pgbouncer, {
|
||||||
|
status: 'offline',
|
||||||
|
activeConnections: 0,
|
||||||
|
waitingClients: 0,
|
||||||
|
poolSize: 0,
|
||||||
|
}),
|
||||||
|
smtp: resolveSettled(smtp, {
|
||||||
|
status: 'offline',
|
||||||
|
lastCheck: new Date().toISOString(),
|
||||||
|
responseTimeMs: -1,
|
||||||
|
}),
|
||||||
|
oauth: resolveSettled(oauth, {
|
||||||
|
metaOAuth: { status: 'error', tokensTotal: 0, tokensExpiringSoon: 0, tokensExpired: 0 },
|
||||||
|
youtubeOAuth: { status: 'error', tokensTotal: 0, tokensExpiringSoon: 0, tokensExpired: 0 },
|
||||||
|
}),
|
||||||
|
cronJobs: resolveSettled(cronJobs, {
|
||||||
|
communitySync: { lastRun: '', status: 'unknown' },
|
||||||
|
tokenRefresh: { lastRun: '', status: 'unknown' },
|
||||||
|
youtubeSync: { lastRun: '', status: 'unknown' },
|
||||||
}),
|
}),
|
||||||
cronJobs: resolveSettled(cronJobs, {}),
|
|
||||||
queues: resolveSettled(queues, {}),
|
queues: resolveSettled(queues, {}),
|
||||||
},
|
},
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export async function GET(request: NextRequest): Promise<Response> {
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
const { user } = await payload.auth({ headers: request.headers })
|
const { user } = await payload.auth({ headers: request.headers })
|
||||||
|
|
||||||
if (!user || !(user as Record<string, unknown>).isSuperAdmin) {
|
if (!user || !(user as { isSuperAdmin?: boolean }).isSuperAdmin) {
|
||||||
return new Response('Unauthorized', { status: 401 })
|
return new Response('Unauthorized', { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,25 @@ import config from '@payload-config'
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
|
||||||
// Lazy imports für Security-Module um Initialisierungsfehler zu vermeiden
|
// Lazy imports für Security-Module um Initialisierungsfehler zu vermeiden
|
||||||
let securityModules: {
|
type SecurityModules = {
|
||||||
authLimiter?: { check: (ip: string) => Promise<{ allowed: boolean; remaining: number; resetIn: number }> }
|
authLimiter?: { check: (ip: string) => Promise<{ allowed: boolean; remaining: number; resetIn: number }> }
|
||||||
rateLimitHeaders?: (result: { remaining: number; resetIn: number }, max: number) => Record<string, string>
|
rateLimitHeaders?: (result: unknown, max: number) => Record<string, string>
|
||||||
getClientIpFromRequest?: (req: NextRequest) => string
|
getClientIpFromRequest?: (req: NextRequest) => string
|
||||||
isIpBlocked?: (ip: string) => boolean
|
isIpBlocked?: (ip: string) => boolean
|
||||||
validateCsrf?: (req: NextRequest) => { valid: boolean; reason?: string }
|
validateCsrf?: (req: NextRequest) => { valid: boolean; reason?: string }
|
||||||
} | null = null
|
}
|
||||||
|
|
||||||
async function getSecurityModules() {
|
let securityModules: SecurityModules = {}
|
||||||
if (securityModules) return securityModules
|
let securityModulesLoaded = false
|
||||||
|
|
||||||
|
async function getSecurityModules(): Promise<SecurityModules> {
|
||||||
|
if (securityModulesLoaded) return securityModules
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const security = await import('@/lib/security')
|
const security = await import('@/lib/security')
|
||||||
securityModules = {
|
securityModules = {
|
||||||
authLimiter: security.authLimiter,
|
authLimiter: security.authLimiter,
|
||||||
rateLimitHeaders: security.rateLimitHeaders,
|
rateLimitHeaders: security.rateLimitHeaders as SecurityModules['rateLimitHeaders'],
|
||||||
getClientIpFromRequest: security.getClientIpFromRequest,
|
getClientIpFromRequest: security.getClientIpFromRequest,
|
||||||
isIpBlocked: security.isIpBlocked,
|
isIpBlocked: security.isIpBlocked,
|
||||||
validateCsrf: security.validateCsrf,
|
validateCsrf: security.validateCsrf,
|
||||||
|
|
@ -43,29 +46,46 @@ async function getSecurityModules() {
|
||||||
securityModules = {}
|
securityModules = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
securityModulesLoaded = true
|
||||||
|
|
||||||
return securityModules
|
return securityModules
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy import für Audit-Service
|
// Lazy import für Audit-Service
|
||||||
let auditService: {
|
type AuditService = {
|
||||||
logLoginFailed?: (payload: unknown, email: string, reason: string, clientInfo: { ipAddress: string; userAgent: string }) => Promise<void>
|
logLoginFailed?: (
|
||||||
logRateLimit?: (payload: unknown, endpoint: string, userId?: number, tenantId?: number) => Promise<void>
|
payload: unknown,
|
||||||
} | null = null
|
email: string,
|
||||||
|
reason: string,
|
||||||
|
clientInfo: { ipAddress: string; userAgent: string },
|
||||||
|
) => Promise<void>
|
||||||
|
logRateLimit?: (
|
||||||
|
payload: unknown,
|
||||||
|
endpoint: string,
|
||||||
|
userId?: number,
|
||||||
|
tenantId?: number,
|
||||||
|
) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
async function getAuditService() {
|
let auditService: AuditService = {}
|
||||||
if (auditService) return auditService
|
let auditServiceLoaded = false
|
||||||
|
|
||||||
|
async function getAuditService(): Promise<AuditService> {
|
||||||
|
if (auditServiceLoaded) return auditService
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const audit = await import('@/lib/audit/audit-service')
|
const audit = await import('@/lib/audit/audit-service')
|
||||||
auditService = {
|
auditService = {
|
||||||
logLoginFailed: audit.logLoginFailed,
|
logLoginFailed: audit.logLoginFailed as AuditService['logLoginFailed'],
|
||||||
logRateLimit: audit.logRateLimit,
|
logRateLimit: audit.logRateLimit as AuditService['logRateLimit'],
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[Login] Audit service not available:', err)
|
console.warn('[Login] Audit service not available:', err)
|
||||||
auditService = {}
|
auditService = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auditServiceLoaded = true
|
||||||
|
|
||||||
return auditService
|
return auditService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
return NextResponse.json({ error: 'start and end required' }, { status: 400 })
|
return NextResponse.json({ error: 'start and end required' }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const where: Record<string, unknown> = {
|
const where: any = {
|
||||||
scheduledPublishDate: {
|
scheduledPublishDate: {
|
||||||
greater_than_equal: start,
|
greater_than_equal: start,
|
||||||
less_than_equal: end,
|
less_than_equal: end,
|
||||||
|
|
@ -74,15 +74,13 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
// Build channel color lookup
|
// Build channel color lookup
|
||||||
const channelColorMap = new Map<number, string>()
|
const channelColorMap = new Map<number, string>()
|
||||||
for (const ch of channels.docs) {
|
for (const ch of channels.docs) {
|
||||||
const branding = (ch as Record<string, unknown>).branding as
|
const branding = (ch as unknown as { branding?: { primaryColor?: string } }).branding
|
||||||
| { primaryColor?: string }
|
|
||||||
| undefined
|
|
||||||
channelColorMap.set(ch.id as number, branding?.primaryColor || '#3788d8')
|
channelColorMap.set(ch.id as number, branding?.primaryColor || '#3788d8')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build CalendarEvent array for conflict detection
|
// Build CalendarEvent array for conflict detection
|
||||||
const calendarEvents: CalendarEvent[] = videos.docs.map((v) => {
|
const calendarEvents: CalendarEvent[] = videos.docs.map((v) => {
|
||||||
const doc = v as Record<string, unknown>
|
const doc = v as unknown as Record<string, unknown>
|
||||||
const channel = resolveRelation(doc.channel)
|
const channel = resolveRelation(doc.channel)
|
||||||
const series = resolveRelation(doc.series)
|
const series = resolveRelation(doc.series)
|
||||||
|
|
||||||
|
|
@ -97,7 +95,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
|
|
||||||
// Get schedule config from first channel
|
// Get schedule config from first channel
|
||||||
const defaultSchedule = { longformPerWeek: 1, shortsPerWeek: 4 }
|
const defaultSchedule = { longformPerWeek: 1, shortsPerWeek: 4 }
|
||||||
const firstChannel = channels.docs[0] as Record<string, unknown> | undefined
|
const firstChannel = channels.docs[0] as unknown as Record<string, unknown> | undefined
|
||||||
const publishingSchedule = firstChannel?.publishingSchedule as
|
const publishingSchedule = firstChannel?.publishingSchedule as
|
||||||
| { longformPerWeek?: number; shortsPerWeek?: number }
|
| { longformPerWeek?: number; shortsPerWeek?: number }
|
||||||
| undefined
|
| undefined
|
||||||
|
|
@ -111,7 +109,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
|
|
||||||
// Format for FullCalendar
|
// Format for FullCalendar
|
||||||
const events = videos.docs.map((v) => {
|
const events = videos.docs.map((v) => {
|
||||||
const doc = v as Record<string, unknown>
|
const doc = v as unknown as Record<string, unknown>
|
||||||
const channel = resolveRelation(doc.channel)
|
const channel = resolveRelation(doc.channel)
|
||||||
const series = resolveRelation(doc.series)
|
const series = resolveRelation(doc.series)
|
||||||
|
|
||||||
|
|
@ -177,7 +175,7 @@ export async function PATCH(req: NextRequest): Promise<NextResponse> {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const status = (content as Record<string, unknown>).status as string
|
const status = (content as unknown as Record<string, unknown>).status as string
|
||||||
if (status === 'published' || status === 'tracked') {
|
if (status === 'published' || status === 'tracked') {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Veröffentlichte Videos können nicht verschoben werden' },
|
{ error: 'Veröffentlichte Videos können nicht verschoben werden' },
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,15 @@ export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const thumbnailUrl = getYouTubeThumbnail(videoId, 'hq')
|
const thumbnailUrl = getYouTubeThumbnail(videoId, 'hq')
|
||||||
const filename = `yt-thumb-${videoId}.jpg`
|
const filename = `yt-thumb-${videoId}.jpg`
|
||||||
const tenantId = typeof doc.tenant === 'object' ? doc.tenant?.id : doc.tenant
|
const title =
|
||||||
|
typeof doc.title === 'string'
|
||||||
|
? doc.title
|
||||||
|
: `YouTube Video ${videoId}`
|
||||||
|
|
||||||
const mediaId = await downloadAndUploadImage(payload, {
|
const mediaId = await downloadAndUploadImage(payload, {
|
||||||
url: thumbnailUrl,
|
url: thumbnailUrl,
|
||||||
filename,
|
filename,
|
||||||
alt: `Thumbnail: ${typeof doc.title === 'string' ? doc.title : doc.title?.de || videoId}`,
|
alt: `Thumbnail: ${title}`,
|
||||||
tenantId: tenantId || undefined,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (mediaId) {
|
if (mediaId) {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
return NextResponse.json({ error: 'Content not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Content not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = content as Record<string, unknown>
|
const doc = content as unknown as Record<string, unknown>
|
||||||
|
|
||||||
if (!doc.videoFile) {
|
if (!doc.videoFile) {
|
||||||
return NextResponse.json({ error: 'No video file attached' }, { status: 400 })
|
return NextResponse.json({ error: 'No video file attached' }, { status: 400 })
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export const ReportSchedules: CollectionConfig = {
|
||||||
width: '50%',
|
width: '50%',
|
||||||
description: 'Format: HH:MM (24-Stunden)',
|
description: 'Format: HH:MM (24-Stunden)',
|
||||||
},
|
},
|
||||||
validate: (value) => {
|
validate: (value: string | null | undefined) => {
|
||||||
if (!value) return true
|
if (!value) return true
|
||||||
const regex = /^([01]\d|2[0-3]):([0-5]\d)$/
|
const regex = /^([01]\d|2[0-3]):([0-5]\d)$/
|
||||||
if (!regex.test(value)) {
|
if (!regex.test(value)) {
|
||||||
|
|
@ -233,7 +233,6 @@ export const ReportSchedules: CollectionConfig = {
|
||||||
|
|
||||||
// === Status (Read-only) ===
|
// === Status (Read-only) ===
|
||||||
{
|
{
|
||||||
name: 'statusSection',
|
|
||||||
type: 'collapsible',
|
type: 'collapsible',
|
||||||
label: 'Status & Statistiken',
|
label: 'Status & Statistiken',
|
||||||
admin: {
|
admin: {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export const YouTubeContent: CollectionConfig = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return true
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
position: 'sidebar',
|
position: 'sidebar',
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ interface ApiResponse {
|
||||||
data: PerformanceData | PipelineData | GoalsData | CommunityData
|
data: PerformanceData | PipelineData | GoalsData | CommunityData
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabConfig: Array<{ key: Tab; label: string; icon: JSX.Element }> = [
|
const tabConfig: Array<{ key: Tab; label: string; icon: React.ReactNode }> = [
|
||||||
{
|
{
|
||||||
key: 'performance',
|
key: 'performance',
|
||||||
label: 'Performance',
|
label: 'Performance',
|
||||||
|
|
@ -201,7 +201,7 @@ const formatDate = (dateString: string): string => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderTrend = (value: number): JSX.Element | null => {
|
const renderTrend = (value: number): React.ReactNode => {
|
||||||
if (value === 0) return null
|
if (value === 0) return null
|
||||||
const isUp = value > 0
|
const isUp = value > 0
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import type { CollectionAfterChangeHook } from 'payload'
|
import type { CollectionAfterChangeHook } from 'payload'
|
||||||
import { NotificationService } from '@/lib/jobs/NotificationService'
|
import { NotificationService } from '@/lib/jobs/NotificationService'
|
||||||
|
import type { YoutubeContent } from '@/payload-types'
|
||||||
|
|
||||||
|
type YouTubeContentStatus = NonNullable<YoutubeContent['status']>
|
||||||
|
|
||||||
interface TransitionContext {
|
interface TransitionContext {
|
||||||
currentStatus: string
|
currentStatus: YouTubeContentStatus
|
||||||
youtubeVideoId?: string | null
|
youtubeVideoId?: string | null
|
||||||
hasAllChecklistsComplete: boolean
|
hasAllChecklistsComplete: boolean
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +13,7 @@ interface TransitionContext {
|
||||||
/**
|
/**
|
||||||
* Determines the next status based on current state and conditions
|
* Determines the next status based on current state and conditions
|
||||||
*/
|
*/
|
||||||
export function getNextStatus(context: TransitionContext): string | null {
|
export function getNextStatus(context: TransitionContext): YouTubeContentStatus | null {
|
||||||
const { currentStatus, youtubeVideoId } = context
|
const { currentStatus, youtubeVideoId } = context
|
||||||
|
|
||||||
// Upload completed → published
|
// Upload completed → published
|
||||||
|
|
@ -25,7 +28,7 @@ export function getNextStatus(context: TransitionContext): string | null {
|
||||||
* Checks if a manual transition should be suggested
|
* Checks if a manual transition should be suggested
|
||||||
*/
|
*/
|
||||||
export function shouldTransitionStatus(
|
export function shouldTransitionStatus(
|
||||||
status: string,
|
status: YouTubeContentStatus,
|
||||||
context: { hasVideoFile?: boolean },
|
context: { hasVideoFile?: boolean },
|
||||||
): boolean {
|
): boolean {
|
||||||
if (status === 'approved' && context.hasVideoFile) return true
|
if (status === 'approved' && context.hasVideoFile) return true
|
||||||
|
|
@ -44,7 +47,7 @@ export const autoStatusTransitions: CollectionAfterChangeHook = async ({
|
||||||
if (operation !== 'update') return doc
|
if (operation !== 'update') return doc
|
||||||
|
|
||||||
const nextStatus = getNextStatus({
|
const nextStatus = getNextStatus({
|
||||||
currentStatus: doc.status,
|
currentStatus: doc.status as YouTubeContentStatus,
|
||||||
youtubeVideoId: doc.youtube?.videoId,
|
youtubeVideoId: doc.youtube?.videoId,
|
||||||
hasAllChecklistsComplete: false,
|
hasAllChecklistsComplete: false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
// src/hooks/youtubeContent/createTasksOnStatusChange.ts
|
// src/hooks/youtubeContent/createTasksOnStatusChange.ts
|
||||||
|
|
||||||
import type { CollectionAfterChangeHook } from 'payload'
|
import type { CollectionAfterChangeHook } from 'payload'
|
||||||
|
import type { YtTask } from '@/payload-types'
|
||||||
|
|
||||||
|
type TaskType = NonNullable<YtTask['taskType']>
|
||||||
|
type AssignRole = 'creator' | 'producer' | 'editor' | 'manager'
|
||||||
|
|
||||||
interface TaskTemplate {
|
interface TaskTemplate {
|
||||||
title: string
|
title: string
|
||||||
type: string
|
type: TaskType
|
||||||
assignRole: string
|
assignRole: AssignRole
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -121,13 +121,14 @@ export async function runSyncAllCommentsJob(payload: Payload): Promise<{
|
||||||
)
|
)
|
||||||
const syncService = new CommentsSyncService(payload)
|
const syncService = new CommentsSyncService(payload)
|
||||||
|
|
||||||
const result = await syncService.syncCommentsForAccount(account.id, {
|
const result = await syncService.syncComments({
|
||||||
|
socialAccountId: account.id,
|
||||||
maxComments: 100,
|
maxComments: 100,
|
||||||
analyzeWithAI: true,
|
analyzeWithAI: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const created = result.created || 0
|
const created = result.newComments || 0
|
||||||
const updated = result.updated || 0
|
const updated = result.updatedComments || 0
|
||||||
|
|
||||||
totalNew += created
|
totalNew += created
|
||||||
totalUpdated += updated
|
totalUpdated += updated
|
||||||
|
|
|
||||||
|
|
@ -79,5 +79,5 @@ export const canAccessAssignedInteractions: Access = ({ req }) => {
|
||||||
{ assignedTo: { equals: user.id } },
|
{ assignedTo: { equals: user.id } },
|
||||||
{ 'response.sentBy': { equals: user.id } },
|
{ 'response.sentBy': { equals: user.id } },
|
||||||
],
|
],
|
||||||
}
|
} as any
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,9 @@ export class FacebookSyncService {
|
||||||
|
|
||||||
// Platform ID holen
|
// Platform ID holen
|
||||||
const platformId = await this.getPlatformId()
|
const platformId = await this.getPlatformId()
|
||||||
|
if (platformId === null) {
|
||||||
|
throw new Error('Facebook platform not configured')
|
||||||
|
}
|
||||||
|
|
||||||
// Interaction-Typ bestimmen (comment oder reply)
|
// Interaction-Typ bestimmen (comment oder reply)
|
||||||
const interactionType = comment.parent?.id ? 'reply' : 'comment'
|
const interactionType = comment.parent?.id ? 'reply' : 'comment'
|
||||||
|
|
@ -299,10 +302,10 @@ export class FacebookSyncService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interaction-Daten zusammenstellen
|
// Interaction-Daten zusammenstellen
|
||||||
const interactionData: Record<string, unknown> = {
|
const interactionData = {
|
||||||
platform: platformId,
|
platform: platformId,
|
||||||
socialAccount: account.id,
|
socialAccount: account.id,
|
||||||
type: interactionType,
|
type: interactionType as 'comment' | 'reply',
|
||||||
externalId: comment.id,
|
externalId: comment.id,
|
||||||
parentInteraction: parentInteractionId,
|
parentInteraction: parentInteractionId,
|
||||||
author: {
|
author: {
|
||||||
|
|
@ -327,7 +330,12 @@ export class FacebookSyncService {
|
||||||
...(comment.attachment && {
|
...(comment.attachment && {
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
type: comment.attachment.type === 'photo' ? 'image' : comment.attachment.type,
|
type:
|
||||||
|
comment.attachment.type === 'photo'
|
||||||
|
? 'image'
|
||||||
|
: comment.attachment.type === 'video'
|
||||||
|
? 'video'
|
||||||
|
: 'link' as 'image' | 'video' | 'link',
|
||||||
url: comment.attachment.url || comment.attachment.media?.image?.src,
|
url: comment.attachment.url || comment.attachment.media?.image?.src,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -352,6 +360,8 @@ export class FacebookSyncService {
|
||||||
}),
|
}),
|
||||||
// Interne Notizen mit Post-Kontext
|
// Interne Notizen mit Post-Kontext
|
||||||
internalNotes: `Facebook Post: ${post.permalink_url || post.id}`,
|
internalNotes: `Facebook Post: ${post.permalink_url || post.id}`,
|
||||||
|
status: 'new' as const,
|
||||||
|
priority: 'normal' as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
let interactionId: number
|
let interactionId: number
|
||||||
|
|
@ -359,6 +369,7 @@ export class FacebookSyncService {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
const created = await this.payload.create({
|
const created = await this.payload.create({
|
||||||
collection: 'community-interactions',
|
collection: 'community-interactions',
|
||||||
|
draft: false,
|
||||||
data: interactionData,
|
data: interactionData,
|
||||||
})
|
})
|
||||||
interactionId = created.id as number
|
interactionId = created.id as number
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,9 @@ export class InstagramSyncService {
|
||||||
|
|
||||||
// Platform ID holen
|
// Platform ID holen
|
||||||
const platformId = await this.getPlatformId()
|
const platformId = await this.getPlatformId()
|
||||||
|
if (platformId === null) {
|
||||||
|
throw new Error('Instagram platform not configured')
|
||||||
|
}
|
||||||
|
|
||||||
// Interaction-Typ bestimmen
|
// Interaction-Typ bestimmen
|
||||||
const interactionType = parentCommentId ? 'reply' : 'comment'
|
const interactionType = parentCommentId ? 'reply' : 'comment'
|
||||||
|
|
@ -344,10 +347,10 @@ export class InstagramSyncService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interaction-Daten zusammenstellen
|
// Interaction-Daten zusammenstellen
|
||||||
const interactionData: Record<string, unknown> = {
|
const interactionData = {
|
||||||
platform: platformId,
|
platform: platformId,
|
||||||
socialAccount: account.id,
|
socialAccount: account.id,
|
||||||
type: interactionType,
|
type: interactionType as 'comment' | 'reply',
|
||||||
externalId: comment.id,
|
externalId: comment.id,
|
||||||
parentInteraction: parentInteractionId,
|
parentInteraction: parentInteractionId,
|
||||||
author: {
|
author: {
|
||||||
|
|
@ -386,6 +389,8 @@ export class InstagramSyncService {
|
||||||
}),
|
}),
|
||||||
// Interne Notizen mit Media-Kontext
|
// Interne Notizen mit Media-Kontext
|
||||||
internalNotes: `Instagram Post: ${media.permalink}`,
|
internalNotes: `Instagram Post: ${media.permalink}`,
|
||||||
|
status: 'new' as const,
|
||||||
|
priority: 'normal' as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
let interactionId: number
|
let interactionId: number
|
||||||
|
|
@ -393,6 +398,7 @@ export class InstagramSyncService {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
const created = await this.payload.create({
|
const created = await this.payload.create({
|
||||||
collection: 'community-interactions',
|
collection: 'community-interactions',
|
||||||
|
draft: false,
|
||||||
data: interactionData,
|
data: interactionData,
|
||||||
})
|
})
|
||||||
interactionId = created.id as number
|
interactionId = created.id as number
|
||||||
|
|
@ -494,12 +500,15 @@ export class InstagramSyncService {
|
||||||
|
|
||||||
// Platform ID holen
|
// Platform ID holen
|
||||||
const platformId = await this.getPlatformId()
|
const platformId = await this.getPlatformId()
|
||||||
|
if (platformId === null) {
|
||||||
|
throw new Error('Instagram platform not configured')
|
||||||
|
}
|
||||||
|
|
||||||
// Mention als Interaction speichern
|
// Mention als Interaction speichern
|
||||||
const interactionData: Record<string, unknown> = {
|
const interactionData = {
|
||||||
platform: platformId,
|
platform: platformId,
|
||||||
socialAccount: account.id,
|
socialAccount: account.id,
|
||||||
type: 'mention',
|
type: 'mention' as const,
|
||||||
externalId: `mention_${mention.id}`,
|
externalId: `mention_${mention.id}`,
|
||||||
author: {
|
author: {
|
||||||
name: mention.username,
|
name: mention.username,
|
||||||
|
|
@ -528,10 +537,13 @@ export class InstagramSyncService {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
internalNotes: `Instagram Mention: ${mention.permalink}`,
|
internalNotes: `Instagram Mention: ${mention.permalink}`,
|
||||||
|
status: 'new' as const,
|
||||||
|
priority: 'normal' as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.payload.create({
|
await this.payload.create({
|
||||||
collection: 'community-interactions',
|
collection: 'community-interactions',
|
||||||
|
draft: false,
|
||||||
data: interactionData,
|
data: interactionData,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ export class MetaBaseClient {
|
||||||
const nextResponse = await fetch(nextUrl)
|
const nextResponse = await fetch(nextUrl)
|
||||||
const nextData: MetaPaginatedResponse<T> = await nextResponse.json()
|
const nextData: MetaPaginatedResponse<T> = await nextResponse.json()
|
||||||
|
|
||||||
if (nextData.error) {
|
if ('error' in (nextData as unknown as MetaErrorResponse)) {
|
||||||
throw new MetaApiError((nextData as unknown as MetaErrorResponse).error)
|
throw new MetaApiError((nextData as unknown as MetaErrorResponse).error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,8 @@ export class CommentsSyncService {
|
||||||
isFromInfluencer: false,
|
isFromInfluencer: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
status: 'new' as const,
|
||||||
|
priority: 'normal' as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentInteractionId: number | null = null
|
let parentInteractionId: number | null = null
|
||||||
|
|
@ -238,6 +240,7 @@ export class CommentsSyncService {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
const created = await this.payload.create({
|
const created = await this.payload.create({
|
||||||
collection: 'community-interactions',
|
collection: 'community-interactions',
|
||||||
|
draft: false,
|
||||||
data: interactionData,
|
data: interactionData,
|
||||||
})
|
})
|
||||||
parentInteractionId = created.id
|
parentInteractionId = created.id
|
||||||
|
|
@ -314,6 +317,7 @@ export class CommentsSyncService {
|
||||||
|
|
||||||
await this.payload.create({
|
await this.payload.create({
|
||||||
collection: 'community-interactions',
|
collection: 'community-interactions',
|
||||||
|
draft: false,
|
||||||
data: {
|
data: {
|
||||||
platform: platformId,
|
platform: platformId,
|
||||||
socialAccount: account.id,
|
socialAccount: account.id,
|
||||||
|
|
@ -356,6 +360,8 @@ export class CommentsSyncService {
|
||||||
isFromInfluencer: false,
|
isFromInfluencer: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
status: 'new' as const,
|
||||||
|
priority: 'normal' as const,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ interface CommentThread {
|
||||||
|
|
||||||
export class YouTubeClient {
|
export class YouTubeClient {
|
||||||
private youtube: youtube_v3.Youtube
|
private youtube: youtube_v3.Youtube
|
||||||
private oauth2Client: ReturnType<typeof google.auth.OAuth2>
|
private oauth2Client: InstanceType<typeof google.auth.OAuth2>
|
||||||
private payload: Payload
|
private payload: Payload
|
||||||
|
|
||||||
constructor(credentials: YouTubeCredentials, payload: Payload) {
|
constructor(credentials: YouTubeCredentials, payload: Payload) {
|
||||||
|
|
|
||||||
|
|
@ -60,13 +60,13 @@ export function evaluateCondition(
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
interface AlertRule {
|
interface AlertRule {
|
||||||
id: string
|
id: number
|
||||||
name: string
|
name: string
|
||||||
metric: string
|
metric: string
|
||||||
condition: AlertCondition
|
condition: AlertCondition
|
||||||
threshold: number
|
threshold: number
|
||||||
severity: AlertSeverity
|
severity: AlertSeverity
|
||||||
channels: string[]
|
channels: Array<'email' | 'slack' | 'discord'>
|
||||||
recipients?: {
|
recipients?: {
|
||||||
emails?: Array<{ email: string }>
|
emails?: Array<{ email: string }>
|
||||||
slackWebhook?: string
|
slackWebhook?: string
|
||||||
|
|
@ -132,9 +132,10 @@ export class AlertEvaluator {
|
||||||
if (value === undefined) continue
|
if (value === undefined) continue
|
||||||
|
|
||||||
if (evaluateCondition(rule.condition, value, rule.threshold)) {
|
if (evaluateCondition(rule.condition, value, rule.threshold)) {
|
||||||
if (this.shouldFire(rule.id, rule.cooldownMinutes)) {
|
const ruleKey = String(rule.id)
|
||||||
|
if (this.shouldFire(ruleKey, rule.cooldownMinutes)) {
|
||||||
await this.dispatchAlert(payload, rule, value)
|
await this.dispatchAlert(payload, rule, value)
|
||||||
this.recordFired(rule.id)
|
this.recordFired(ruleKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ export interface MonitoringLoggerInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cached Payload instance — resolved once, reused for all subsequent writes. */
|
/** Cached Payload instance — resolved once, reused for all subsequent writes. */
|
||||||
let cachedPayload: { create: (...args: unknown[]) => Promise<unknown> } | null = null
|
let cachedPayload: any = null
|
||||||
|
|
||||||
async function getPayloadInstance() {
|
async function getPayloadInstance(): Promise<any> {
|
||||||
if (cachedPayload) return cachedPayload
|
if (cachedPayload) return cachedPayload
|
||||||
const { getPayload } = await import('payload')
|
const { getPayload } = await import('payload')
|
||||||
const config = (await import(/* @vite-ignore */ '@payload-config')).default
|
const config = (await import(/* @vite-ignore */ '@payload-config')).default
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ export async function checkOAuthTokens(): Promise<{
|
||||||
const youtube = { tokensTotal: 0, tokensExpiringSoon: 0, tokensExpired: 0 }
|
const youtube = { tokensTotal: 0, tokensExpiringSoon: 0, tokensExpired: 0 }
|
||||||
|
|
||||||
for (const account of accounts.docs) {
|
for (const account of accounts.docs) {
|
||||||
const doc = account as Record<string, unknown>
|
const doc = account as unknown as Record<string, unknown>
|
||||||
const target = doc.platform === 'youtube' ? youtube : meta
|
const target = doc.platform === 'youtube' ? youtube : meta
|
||||||
target.tokensTotal++
|
target.tokensTotal++
|
||||||
|
|
||||||
|
|
@ -304,7 +304,7 @@ export async function checkCronJobs(): Promise<CronStatuses> {
|
||||||
|
|
||||||
if (logs.docs.length === 0) return unknownStatus
|
if (logs.docs.length === 0) return unknownStatus
|
||||||
|
|
||||||
const doc = logs.docs[0] as Record<string, unknown>
|
const doc = logs.docs[0] as unknown as Record<string, unknown>
|
||||||
return {
|
return {
|
||||||
lastRun: doc.createdAt as string,
|
lastRun: doc.createdAt as string,
|
||||||
status: doc.level === 'error' ? 'failed' : 'ok',
|
status: doc.level === 'error' ? 'failed' : 'ok',
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ let interval: ReturnType<typeof setInterval> | null = null
|
||||||
const alertEvaluator = new AlertEvaluator()
|
const alertEvaluator = new AlertEvaluator()
|
||||||
|
|
||||||
/** Cached Payload instance — resolved once, reused on every tick. */
|
/** Cached Payload instance — resolved once, reused on every tick. */
|
||||||
let cachedPayload: { create: (...args: unknown[]) => Promise<unknown>; find: (...args: unknown[]) => Promise<unknown> } | null = null
|
let cachedPayload: any = null
|
||||||
|
|
||||||
async function getPayloadInstance() {
|
async function getPayloadInstance(): Promise<any> {
|
||||||
if (cachedPayload) return cachedPayload
|
if (cachedPayload) return cachedPayload
|
||||||
const { getPayload } = await import('payload')
|
const { getPayload } = await import('payload')
|
||||||
const config = (await import(/* @vite-ignore */ '@payload-config')).default
|
const config = (await import(/* @vite-ignore */ '@payload-config')).default
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ export class ReportGeneratorService {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
overrideAccess: true,
|
overrideAccess: true,
|
||||||
})
|
})
|
||||||
const tenant = (account as Record<string, unknown>)?.tenant
|
const tenant = (account as unknown as Record<string, unknown>)?.tenant
|
||||||
if (typeof tenant === 'number') return tenant
|
if (typeof tenant === 'number') return tenant
|
||||||
if (typeof tenant === 'object' && tenant && 'id' in (tenant as Record<string, unknown>)) {
|
if (typeof tenant === 'object' && tenant && 'id' in (tenant as Record<string, unknown>)) {
|
||||||
return (tenant as { id: number }).id
|
return (tenant as { id: number }).id
|
||||||
|
|
@ -198,12 +198,13 @@ export class ReportGeneratorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update schedule stats
|
// Update schedule stats
|
||||||
|
const existingSendCount = (await this.getSchedule(schedule.id))?.sendCount ?? 0
|
||||||
await this.payload.update({
|
await this.payload.update({
|
||||||
collection: 'report-schedules',
|
collection: 'report-schedules',
|
||||||
id: schedule.id,
|
id: schedule.id,
|
||||||
data: {
|
data: {
|
||||||
lastSentAt: new Date().toISOString(),
|
lastSentAt: new Date().toISOString(),
|
||||||
sendCount: (await this.getSchedule(schedule.id))?.sendCount + 1 || 1,
|
sendCount: existingSendCount + 1,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -289,10 +290,12 @@ export class ReportGeneratorService {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const replied = interactions.docs.filter((i) => i.status === 'replied')
|
const docs = interactions.docs as any[]
|
||||||
const platforms = interactions.docs.reduce(
|
|
||||||
|
const replied = docs.filter((i) => i.status === 'replied')
|
||||||
|
const platforms = docs.reduce(
|
||||||
(acc, i) => {
|
(acc, i) => {
|
||||||
const platform = (i.platform as string) || 'unknown'
|
const platform = this.resolvePlatformName(i.platform)
|
||||||
acc[platform] = (acc[platform] || 0) + 1
|
acc[platform] = (acc[platform] || 0) + 1
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
|
|
@ -303,9 +306,9 @@ export class ReportGeneratorService {
|
||||||
let totalResponseTime = 0
|
let totalResponseTime = 0
|
||||||
let responseCount = 0
|
let responseCount = 0
|
||||||
for (const interaction of replied) {
|
for (const interaction of replied) {
|
||||||
if (interaction.repliedAt && interaction.createdAt) {
|
if (interaction.response?.sentAt && interaction.createdAt) {
|
||||||
const responseTime =
|
const responseTime =
|
||||||
new Date(interaction.repliedAt as string).getTime() -
|
new Date(interaction.response.sentAt as string).getTime() -
|
||||||
new Date(interaction.createdAt as string).getTime()
|
new Date(interaction.createdAt as string).getTime()
|
||||||
totalResponseTime += responseTime
|
totalResponseTime += responseTime
|
||||||
responseCount++
|
responseCount++
|
||||||
|
|
@ -314,10 +317,13 @@ export class ReportGeneratorService {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalInteractions: interactions.totalDocs,
|
totalInteractions: interactions.totalDocs,
|
||||||
newInteractions: interactions.docs.filter((i) => i.status === 'new').length,
|
newInteractions: docs.filter((i) => i.status === 'new').length,
|
||||||
repliedCount: replied.length,
|
repliedCount: replied.length,
|
||||||
avgResponseTime: responseCount > 0 ? totalResponseTime / responseCount / (1000 * 60 * 60) : 0,
|
avgResponseTime: responseCount > 0 ? totalResponseTime / responseCount / (1000 * 60 * 60) : 0,
|
||||||
platforms: Object.entries(platforms).map(([platform, count]) => ({ platform, count })),
|
platforms: Object.entries(platforms).map(([platform, count]) => ({
|
||||||
|
platform,
|
||||||
|
count: typeof count === 'number' ? count : 0,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,14 +348,16 @@ export class ReportGeneratorService {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const docs = interactions.docs as any[]
|
||||||
|
|
||||||
let positive = 0
|
let positive = 0
|
||||||
let neutral = 0
|
let neutral = 0
|
||||||
let negative = 0
|
let negative = 0
|
||||||
|
|
||||||
const dailyData: Record<string, { positive: number; neutral: number; negative: number }> = {}
|
const dailyData: Record<string, { positive: number; neutral: number; negative: number }> = {}
|
||||||
|
|
||||||
for (const interaction of interactions.docs) {
|
for (const interaction of docs) {
|
||||||
const sentiment = (interaction.aiAnalysis as any)?.sentiment || 'neutral'
|
const sentiment = interaction.analysis?.sentiment || 'neutral'
|
||||||
const date = new Date(interaction.createdAt as string).toISOString().split('T')[0]
|
const date = new Date(interaction.createdAt as string).toISOString().split('T')[0]
|
||||||
|
|
||||||
if (!dailyData[date]) {
|
if (!dailyData[date]) {
|
||||||
|
|
@ -399,24 +407,26 @@ export class ReportGeneratorService {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const replied = interactions.docs.filter((i) => i.status === 'replied')
|
const docs = interactions.docs as any[]
|
||||||
const pending = interactions.docs.filter((i) => i.status === 'new' || i.status === 'in_review')
|
|
||||||
|
const replied = docs.filter((i) => i.status === 'replied')
|
||||||
|
const pending = docs.filter((i) => i.status === 'new' || i.status === 'in_review')
|
||||||
|
|
||||||
// Berechne durchschnittliche Antwortzeit
|
// Berechne durchschnittliche Antwortzeit
|
||||||
let totalTime = 0
|
let totalTime = 0
|
||||||
let count = 0
|
let count = 0
|
||||||
const platformData: Record<string, { totalTime: number; count: number; replied: number }> = {}
|
const platformData: Record<string, { totalTime: number; count: number; replied: number }> = {}
|
||||||
|
|
||||||
for (const interaction of interactions.docs) {
|
for (const interaction of docs) {
|
||||||
const platform = (interaction.platform as string) || 'unknown'
|
const platform = this.resolvePlatformName(interaction.platform)
|
||||||
if (!platformData[platform]) {
|
if (!platformData[platform]) {
|
||||||
platformData[platform] = { totalTime: 0, count: 0, replied: 0 }
|
platformData[platform] = { totalTime: 0, count: 0, replied: 0 }
|
||||||
}
|
}
|
||||||
platformData[platform].count++
|
platformData[platform].count++
|
||||||
|
|
||||||
if (interaction.status === 'replied' && interaction.repliedAt && interaction.createdAt) {
|
if (interaction.status === 'replied' && interaction.response?.sentAt && interaction.createdAt) {
|
||||||
const responseTime =
|
const responseTime =
|
||||||
new Date(interaction.repliedAt as string).getTime() -
|
new Date(interaction.response.sentAt as string).getTime() -
|
||||||
new Date(interaction.createdAt as string).getTime()
|
new Date(interaction.createdAt as string).getTime()
|
||||||
totalTime += responseTime
|
totalTime += responseTime
|
||||||
count++
|
count++
|
||||||
|
|
@ -459,14 +469,22 @@ export class ReportGeneratorService {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const docs = interactions.docs as any[]
|
||||||
|
|
||||||
// Top Posts nach Kommentaren gruppieren
|
// Top Posts nach Kommentaren gruppieren
|
||||||
const postData: Record<string, { title: string; platform: string; comments: number }> = {}
|
const postData: Record<string, { title: string; platform: string; comments: number }> = {}
|
||||||
const topicCount: Record<string, number> = {}
|
const topicCount: Record<string, number> = {}
|
||||||
|
|
||||||
for (const interaction of interactions.docs) {
|
for (const interaction of docs) {
|
||||||
const postId = (interaction.sourceId as string) || 'unknown'
|
const linkedContent = interaction.linkedContent
|
||||||
const platform = (interaction.platform as string) || 'unknown'
|
const postId =
|
||||||
const title = (interaction.sourceTitle as string) || postId
|
typeof linkedContent === 'object' && linkedContent && 'id' in linkedContent
|
||||||
|
? String((linkedContent as { id: number }).id)
|
||||||
|
: typeof linkedContent === 'number'
|
||||||
|
? String(linkedContent)
|
||||||
|
: 'unknown'
|
||||||
|
const platform = this.resolvePlatformName(interaction.platform)
|
||||||
|
const title = this.resolveContentTitle(linkedContent, postId)
|
||||||
|
|
||||||
if (!postData[postId]) {
|
if (!postData[postId]) {
|
||||||
postData[postId] = { title, platform, comments: 0 }
|
postData[postId] = { title, platform, comments: 0 }
|
||||||
|
|
@ -474,9 +492,16 @@ export class ReportGeneratorService {
|
||||||
postData[postId].comments++
|
postData[postId].comments++
|
||||||
|
|
||||||
// Topics aus AI-Analyse sammeln
|
// Topics aus AI-Analyse sammeln
|
||||||
const topics = (interaction.aiAnalysis as any)?.topics || []
|
const topics = Array.isArray(interaction.analysis?.topics)
|
||||||
|
? interaction.analysis.topics
|
||||||
|
: []
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
topicCount[topic] = (topicCount[topic] || 0) + 1
|
const topicName =
|
||||||
|
typeof topic === 'string'
|
||||||
|
? topic
|
||||||
|
: (topic as { topic?: string | null })?.topic || ''
|
||||||
|
if (!topicName) continue
|
||||||
|
topicCount[topicName] = (topicCount[topicName] || 0) + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,7 +527,7 @@ export class ReportGeneratorService {
|
||||||
}.pdf`
|
}.pdf`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await generatePdfFromHtml(html, { filename })
|
const result = await generatePdfFromHtml(html)
|
||||||
|
|
||||||
if (!result.success || !result.buffer) {
|
if (!result.success || !result.buffer) {
|
||||||
throw new Error(result.error || 'PDF generation failed')
|
throw new Error(result.error || 'PDF generation failed')
|
||||||
|
|
@ -753,6 +778,27 @@ export class ReportGeneratorService {
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolvePlatformName(platform: unknown): string {
|
||||||
|
if (typeof platform === 'string') return platform
|
||||||
|
if (typeof platform === 'number') return String(platform)
|
||||||
|
if (platform && typeof platform === 'object') {
|
||||||
|
const p = platform as { slug?: string; name?: string }
|
||||||
|
return p.slug || p.name || 'unknown'
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveContentTitle(linkedContent: unknown, fallbackId: string): string {
|
||||||
|
if (!linkedContent || typeof linkedContent !== 'object') return fallbackId
|
||||||
|
const content = linkedContent as { title?: unknown }
|
||||||
|
if (typeof content.title === 'string') return content.title
|
||||||
|
if (content.title && typeof content.title === 'object') {
|
||||||
|
const localized = content.title as { de?: string; en?: string }
|
||||||
|
return localized.de || localized.en || fallbackId
|
||||||
|
}
|
||||||
|
return fallbackId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generiert einfachen E-Mail-Body für Attachments
|
* Generiert einfachen E-Mail-Body für Attachments
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -289,20 +289,21 @@ export class RulesEngine {
|
||||||
if (!userId) return
|
if (!userId) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const relatedVideo =
|
||||||
|
typeof interaction.linkedContent === 'object'
|
||||||
|
? interaction.linkedContent.id
|
||||||
|
: interaction.linkedContent
|
||||||
|
|
||||||
// Prüfen ob yt-notifications Collection existiert
|
// Prüfen ob yt-notifications Collection existiert
|
||||||
await this.payload.create({
|
await this.payload.create({
|
||||||
collection: 'yt-notifications',
|
collection: 'yt-notifications',
|
||||||
data: {
|
data: {
|
||||||
user: userId,
|
recipient: userId,
|
||||||
type: 'community_rule_triggered',
|
type: 'system',
|
||||||
title: `🔔 Rule "${rule.name}" triggered`,
|
title: `🔔 Rule "${rule.name}" triggered`,
|
||||||
message: `New interaction from ${interaction.author?.name || 'Unknown'}: "${(interaction.message || '').substring(0, 100)}..."`,
|
message: `New interaction from ${interaction.author?.name || 'Unknown'}: "${(interaction.message || '').substring(0, 100)}..."`,
|
||||||
relatedContent:
|
relatedVideo,
|
||||||
typeof interaction.linkedContent === 'object'
|
read: false,
|
||||||
? interaction.linkedContent.id
|
|
||||||
: interaction.linkedContent,
|
|
||||||
priority: interaction.priority === 'urgent' ? 'urgent' : 'normal',
|
|
||||||
isRead: false,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export const canAccessAssignedContent: Access = ({ req }) => {
|
||||||
{ assignedTo: { equals: user.id } },
|
{ assignedTo: { equals: user.id } },
|
||||||
{ createdBy: { equals: user.id } },
|
{ createdBy: { equals: user.id } },
|
||||||
],
|
],
|
||||||
}
|
} as any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,10 @@ export interface Config {
|
||||||
'privacy-policy-settings': PrivacyPolicySetting;
|
'privacy-policy-settings': PrivacyPolicySetting;
|
||||||
'email-logs': EmailLog;
|
'email-logs': EmailLog;
|
||||||
'audit-logs': AuditLog;
|
'audit-logs': AuditLog;
|
||||||
|
'monitoring-snapshots': MonitoringSnapshot;
|
||||||
|
'monitoring-logs': MonitoringLog;
|
||||||
|
'monitoring-alert-rules': MonitoringAlertRule;
|
||||||
|
'monitoring-alert-history': MonitoringAlertHistory;
|
||||||
'site-settings': SiteSetting;
|
'site-settings': SiteSetting;
|
||||||
navigations: Navigation;
|
navigations: Navigation;
|
||||||
forms: Form;
|
forms: Form;
|
||||||
|
|
@ -187,6 +191,10 @@ export interface Config {
|
||||||
'privacy-policy-settings': PrivacyPolicySettingsSelect<false> | PrivacyPolicySettingsSelect<true>;
|
'privacy-policy-settings': PrivacyPolicySettingsSelect<false> | PrivacyPolicySettingsSelect<true>;
|
||||||
'email-logs': EmailLogsSelect<false> | EmailLogsSelect<true>;
|
'email-logs': EmailLogsSelect<false> | EmailLogsSelect<true>;
|
||||||
'audit-logs': AuditLogsSelect<false> | AuditLogsSelect<true>;
|
'audit-logs': AuditLogsSelect<false> | AuditLogsSelect<true>;
|
||||||
|
'monitoring-snapshots': MonitoringSnapshotsSelect<false> | MonitoringSnapshotsSelect<true>;
|
||||||
|
'monitoring-logs': MonitoringLogsSelect<false> | MonitoringLogsSelect<true>;
|
||||||
|
'monitoring-alert-rules': MonitoringAlertRulesSelect<false> | MonitoringAlertRulesSelect<true>;
|
||||||
|
'monitoring-alert-history': MonitoringAlertHistorySelect<false> | MonitoringAlertHistorySelect<true>;
|
||||||
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
|
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
|
||||||
navigations: NavigationsSelect<false> | NavigationsSelect<true>;
|
navigations: NavigationsSelect<false> | NavigationsSelect<true>;
|
||||||
forms: FormsSelect<false> | FormsSelect<true>;
|
forms: FormsSelect<false> | FormsSelect<true>;
|
||||||
|
|
@ -306,6 +314,10 @@ export interface YoutubeChannel {
|
||||||
language: 'de' | 'en';
|
language: 'de' | 'en';
|
||||||
category: 'lifestyle' | 'corporate' | 'b2b';
|
category: 'lifestyle' | 'corporate' | 'b2b';
|
||||||
status: 'active' | 'planned' | 'paused' | 'archived';
|
status: 'active' | 'planned' | 'paused' | 'archived';
|
||||||
|
/**
|
||||||
|
* Profil-Thumbnail URL von YouTube (automatisch befüllt)
|
||||||
|
*/
|
||||||
|
channelThumbnailUrl?: string | null;
|
||||||
branding?: {
|
branding?: {
|
||||||
/**
|
/**
|
||||||
* z.B. #1278B3
|
* z.B. #1278B3
|
||||||
|
|
@ -490,7 +502,7 @@ export interface Tenant {
|
||||||
/**
|
/**
|
||||||
* Hostname ohne Protokoll (z.B. smtp.gmail.com)
|
* Hostname ohne Protokoll (z.B. smtp.gmail.com)
|
||||||
*/
|
*/
|
||||||
host: string;
|
host?: string | null;
|
||||||
/**
|
/**
|
||||||
* 587 (STARTTLS) oder 465 (SSL)
|
* 587 (STARTTLS) oder 465 (SSL)
|
||||||
*/
|
*/
|
||||||
|
|
@ -502,7 +514,7 @@ export interface Tenant {
|
||||||
/**
|
/**
|
||||||
* Meist die E-Mail-Adresse
|
* Meist die E-Mail-Adresse
|
||||||
*/
|
*/
|
||||||
user: string;
|
user?: string | null;
|
||||||
/**
|
/**
|
||||||
* Leer lassen um bestehendes Passwort zu behalten
|
* Leer lassen um bestehendes Passwort zu behalten
|
||||||
*/
|
*/
|
||||||
|
|
@ -524,11 +536,6 @@ export interface Page {
|
||||||
* URL-Pfad (z.B. "ueber-uns" / "about-us")
|
* URL-Pfad (z.B. "ueber-uns" / "about-us")
|
||||||
*/
|
*/
|
||||||
slug: string;
|
slug: string;
|
||||||
hero?: {
|
|
||||||
image?: (number | null) | Media;
|
|
||||||
headline?: string | null;
|
|
||||||
subline?: string | null;
|
|
||||||
};
|
|
||||||
layout?:
|
layout?:
|
||||||
| (
|
| (
|
||||||
| {
|
| {
|
||||||
|
|
@ -793,7 +800,13 @@ export interface Page {
|
||||||
headline?: string | null;
|
headline?: string | null;
|
||||||
cards?:
|
cards?:
|
||||||
| {
|
| {
|
||||||
|
mediaType?: ('none' | 'image' | 'icon') | null;
|
||||||
image?: (number | null) | Media;
|
image?: (number | null) | Media;
|
||||||
|
/**
|
||||||
|
* Lucide Icon-Name (z.B. "heart", "star", "shield-check", "camera")
|
||||||
|
*/
|
||||||
|
icon?: string | null;
|
||||||
|
iconPosition?: ('top' | 'left') | null;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
link?: string | null;
|
link?: string | null;
|
||||||
|
|
@ -6377,6 +6390,17 @@ export interface YoutubeContent {
|
||||||
subscribersGained?: number | null;
|
subscribersGained?: number | null;
|
||||||
lastSyncedAt?: string | null;
|
lastSyncedAt?: string | null;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Für ROI-Berechnung (manuell pflegen)
|
||||||
|
*/
|
||||||
|
costs?: {
|
||||||
|
estimatedProductionHours?: number | null;
|
||||||
|
estimatedProductionCost?: number | null;
|
||||||
|
/**
|
||||||
|
* AdSense + Sponsoring + Affiliate
|
||||||
|
*/
|
||||||
|
estimatedRevenue?: number | null;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Nur für das Team sichtbar
|
* Nur für das Team sichtbar
|
||||||
*/
|
*/
|
||||||
|
|
@ -7418,6 +7442,232 @@ export interface AuditLog {
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Historische System-Metriken für Trend-Charts
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-snapshots".
|
||||||
|
*/
|
||||||
|
export interface MonitoringSnapshot {
|
||||||
|
id: number;
|
||||||
|
timestamp: string;
|
||||||
|
/**
|
||||||
|
* System-Ressourcen (CPU, RAM, Disk)
|
||||||
|
*/
|
||||||
|
system?: {
|
||||||
|
cpuUsagePercent?: number | null;
|
||||||
|
memoryUsedMB?: number | null;
|
||||||
|
memoryTotalMB?: number | null;
|
||||||
|
memoryUsagePercent?: number | null;
|
||||||
|
diskUsedGB?: number | null;
|
||||||
|
diskTotalGB?: number | null;
|
||||||
|
diskUsagePercent?: number | null;
|
||||||
|
loadAvg1?: number | null;
|
||||||
|
loadAvg5?: number | null;
|
||||||
|
/**
|
||||||
|
* Uptime in Sekunden
|
||||||
|
*/
|
||||||
|
uptime?: number | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Service-Status (PM2-Prozesse, Datenbank, Cache)
|
||||||
|
*/
|
||||||
|
services?: {
|
||||||
|
payload?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
queueWorker?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
postgresql?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
pgbouncer?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
redis?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Externe Services (SMTP, OAuth, Cron)
|
||||||
|
*/
|
||||||
|
external?: {
|
||||||
|
smtp?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
metaOAuth?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
youtubeOAuth?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
cronJobs?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Performance-Metriken
|
||||||
|
*/
|
||||||
|
performance?: {
|
||||||
|
avgResponseTimeMs?: number | null;
|
||||||
|
p95ResponseTimeMs?: number | null;
|
||||||
|
p99ResponseTimeMs?: number | null;
|
||||||
|
errorRate?: number | null;
|
||||||
|
requestsPerMinute?: number | null;
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Structured Logs für Business-Events
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-logs".
|
||||||
|
*/
|
||||||
|
export interface MonitoringLog {
|
||||||
|
id: number;
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||||
|
source: 'payload' | 'queue-worker' | 'cron' | 'email' | 'oauth' | 'sync';
|
||||||
|
message: string;
|
||||||
|
/**
|
||||||
|
* Strukturierte Metadaten
|
||||||
|
*/
|
||||||
|
context?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
/**
|
||||||
|
* Korrelations-ID
|
||||||
|
*/
|
||||||
|
requestId?: string | null;
|
||||||
|
userId?: (number | null) | User;
|
||||||
|
tenant?: (number | null) | Tenant;
|
||||||
|
/**
|
||||||
|
* Dauer in ms
|
||||||
|
*/
|
||||||
|
duration?: number | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Konfigurierbare Alert-Regeln
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-alert-rules".
|
||||||
|
*/
|
||||||
|
export interface MonitoringAlertRule {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* z.B. system.cpuUsagePercent, services.redis.memoryUsedMB
|
||||||
|
*/
|
||||||
|
metric: string;
|
||||||
|
condition: 'gt' | 'lt' | 'eq' | 'gte' | 'lte';
|
||||||
|
threshold: number;
|
||||||
|
severity: 'warning' | 'error' | 'critical';
|
||||||
|
channels: ('email' | 'slack' | 'discord')[];
|
||||||
|
recipients?: {
|
||||||
|
emails?:
|
||||||
|
| {
|
||||||
|
email: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
slackWebhook?: string | null;
|
||||||
|
discordWebhook?: string | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Minimaler Abstand zwischen gleichen Alerts
|
||||||
|
*/
|
||||||
|
cooldownMinutes?: number | null;
|
||||||
|
enabled?: boolean | null;
|
||||||
|
/**
|
||||||
|
* Optional: Tenant-spezifische Regel
|
||||||
|
*/
|
||||||
|
tenant?: (number | null) | Tenant;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Alert-Log (WORM - Write Once)
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-alert-history".
|
||||||
|
*/
|
||||||
|
export interface MonitoringAlertHistory {
|
||||||
|
id: number;
|
||||||
|
rule?: (number | null) | MonitoringAlertRule;
|
||||||
|
metric: string;
|
||||||
|
value: number;
|
||||||
|
threshold: number;
|
||||||
|
severity: 'warning' | 'error' | 'critical';
|
||||||
|
message: string;
|
||||||
|
channelsSent?: ('email' | 'slack' | 'discord')[] | null;
|
||||||
|
resolvedAt?: string | null;
|
||||||
|
acknowledgedBy?: (number | null) | User;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Allgemeine Website-Einstellungen pro Tenant
|
* Allgemeine Website-Einstellungen pro Tenant
|
||||||
*
|
*
|
||||||
|
|
@ -7800,6 +8050,22 @@ export interface PayloadLockedDocument {
|
||||||
relationTo: 'audit-logs';
|
relationTo: 'audit-logs';
|
||||||
value: number | AuditLog;
|
value: number | AuditLog;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'monitoring-snapshots';
|
||||||
|
value: number | MonitoringSnapshot;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'monitoring-logs';
|
||||||
|
value: number | MonitoringLog;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'monitoring-alert-rules';
|
||||||
|
value: number | MonitoringAlertRule;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'monitoring-alert-history';
|
||||||
|
value: number | MonitoringAlertHistory;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'site-settings';
|
relationTo: 'site-settings';
|
||||||
value: number | SiteSetting;
|
value: number | SiteSetting;
|
||||||
|
|
@ -8061,13 +8327,6 @@ export interface PagesSelect<T extends boolean = true> {
|
||||||
tenant?: T;
|
tenant?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
slug?: T;
|
slug?: T;
|
||||||
hero?:
|
|
||||||
| T
|
|
||||||
| {
|
|
||||||
image?: T;
|
|
||||||
headline?: T;
|
|
||||||
subline?: T;
|
|
||||||
};
|
|
||||||
layout?:
|
layout?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
|
@ -8293,7 +8552,10 @@ export interface PagesSelect<T extends boolean = true> {
|
||||||
cards?:
|
cards?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
mediaType?: T;
|
||||||
image?: T;
|
image?: T;
|
||||||
|
icon?: T;
|
||||||
|
iconPosition?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
link?: T;
|
link?: T;
|
||||||
|
|
@ -11460,6 +11722,7 @@ export interface YoutubeChannelsSelect<T extends boolean = true> {
|
||||||
language?: T;
|
language?: T;
|
||||||
category?: T;
|
category?: T;
|
||||||
status?: T;
|
status?: T;
|
||||||
|
channelThumbnailUrl?: T;
|
||||||
branding?:
|
branding?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
|
@ -11627,6 +11890,13 @@ export interface YoutubeContentSelect<T extends boolean = true> {
|
||||||
subscribersGained?: T;
|
subscribersGained?: T;
|
||||||
lastSyncedAt?: T;
|
lastSyncedAt?: T;
|
||||||
};
|
};
|
||||||
|
costs?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
estimatedProductionHours?: T;
|
||||||
|
estimatedProductionCost?: T;
|
||||||
|
estimatedRevenue?: T;
|
||||||
|
};
|
||||||
internalNotes?: T;
|
internalNotes?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
|
|
@ -12274,6 +12544,117 @@ export interface AuditLogsSelect<T extends boolean = true> {
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-snapshots_select".
|
||||||
|
*/
|
||||||
|
export interface MonitoringSnapshotsSelect<T extends boolean = true> {
|
||||||
|
timestamp?: T;
|
||||||
|
system?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
cpuUsagePercent?: T;
|
||||||
|
memoryUsedMB?: T;
|
||||||
|
memoryTotalMB?: T;
|
||||||
|
memoryUsagePercent?: T;
|
||||||
|
diskUsedGB?: T;
|
||||||
|
diskTotalGB?: T;
|
||||||
|
diskUsagePercent?: T;
|
||||||
|
loadAvg1?: T;
|
||||||
|
loadAvg5?: T;
|
||||||
|
uptime?: T;
|
||||||
|
};
|
||||||
|
services?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
payload?: T;
|
||||||
|
queueWorker?: T;
|
||||||
|
postgresql?: T;
|
||||||
|
pgbouncer?: T;
|
||||||
|
redis?: T;
|
||||||
|
};
|
||||||
|
external?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
smtp?: T;
|
||||||
|
metaOAuth?: T;
|
||||||
|
youtubeOAuth?: T;
|
||||||
|
cronJobs?: T;
|
||||||
|
};
|
||||||
|
performance?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
avgResponseTimeMs?: T;
|
||||||
|
p95ResponseTimeMs?: T;
|
||||||
|
p99ResponseTimeMs?: T;
|
||||||
|
errorRate?: T;
|
||||||
|
requestsPerMinute?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-logs_select".
|
||||||
|
*/
|
||||||
|
export interface MonitoringLogsSelect<T extends boolean = true> {
|
||||||
|
level?: T;
|
||||||
|
source?: T;
|
||||||
|
message?: T;
|
||||||
|
context?: T;
|
||||||
|
requestId?: T;
|
||||||
|
userId?: T;
|
||||||
|
tenant?: T;
|
||||||
|
duration?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-alert-rules_select".
|
||||||
|
*/
|
||||||
|
export interface MonitoringAlertRulesSelect<T extends boolean = true> {
|
||||||
|
name?: T;
|
||||||
|
metric?: T;
|
||||||
|
condition?: T;
|
||||||
|
threshold?: T;
|
||||||
|
severity?: T;
|
||||||
|
channels?: T;
|
||||||
|
recipients?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
emails?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
email?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
slackWebhook?: T;
|
||||||
|
discordWebhook?: T;
|
||||||
|
};
|
||||||
|
cooldownMinutes?: T;
|
||||||
|
enabled?: T;
|
||||||
|
tenant?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "monitoring-alert-history_select".
|
||||||
|
*/
|
||||||
|
export interface MonitoringAlertHistorySelect<T extends boolean = true> {
|
||||||
|
rule?: T;
|
||||||
|
metric?: T;
|
||||||
|
value?: T;
|
||||||
|
threshold?: T;
|
||||||
|
severity?: T;
|
||||||
|
message?: T;
|
||||||
|
channelsSent?: T;
|
||||||
|
resolvedAt?: T;
|
||||||
|
acknowledgedBy?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "site-settings_select".
|
* via the `definition` "site-settings_select".
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue