cms.c2sgmbh/src/jobs/syncAllComments.ts

208 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Sync All Comments Job
*
* Synchronisiert Kommentare für alle aktiven YouTube-Accounts.
* Kann als Cron-Job oder manuell ausgeführt werden.
*/
import { getPayload, Payload } from 'payload'
import config from '@payload-config'
import { refreshAccessToken, isTokenExpired } from '@/lib/integrations/youtube/oauth'
interface SocialAccount {
id: number
displayName?: string
isActive?: boolean
externalId?: string
credentials?: {
accessToken?: string
refreshToken?: string
tokenExpiresAt?: string
}
syncSettings?: {
autoSyncEnabled?: boolean
syncIntervalMinutes?: number
syncComments?: boolean
}
stats?: {
lastSyncedAt?: string
}
}
interface SyncResult {
accountId: number
displayName: string
success: boolean
created: number
updated: number
errors: string[]
}
/**
* Führt den Comment-Sync für alle aktiven Accounts durch
*/
export async function runSyncAllCommentsJob(payload: Payload): Promise<{
totalAccounts: number
successfulSyncs: number
totalNew: number
totalUpdated: number
errors: string[]
}> {
console.log('🔄 Starting scheduled comment sync...')
console.log(` Time: ${new Date().toISOString()}`)
// Aktive Accounts mit Auto-Sync laden
const accounts = await payload.find({
collection: 'social-accounts',
where: {
and: [
{ isActive: { equals: true } },
{ 'syncSettings.autoSyncEnabled': { equals: true } },
{ 'credentials.accessToken': { exists: true } },
],
},
limit: 100,
depth: 2,
})
console.log(`📊 Found ${accounts.docs.length} accounts to sync`)
let totalNew = 0
let totalUpdated = 0
let successfulSyncs = 0
const errors: string[] = []
const results: SyncResult[] = []
for (const doc of accounts.docs) {
const account = doc as unknown as SocialAccount
try {
console.log(`\n📺 Syncing: ${account.displayName || account.id}`)
// Token-Ablauf prüfen und ggf. erneuern
if (isTokenExpired(account.credentials?.tokenExpiresAt)) {
console.log(' ⏰ Token expired, refreshing...')
if (!account.credentials?.refreshToken) {
console.log(' ❌ No refresh token available')
errors.push(`${account.displayName}: No refresh token available`)
continue
}
try {
const newCredentials = await refreshAccessToken(account.credentials.refreshToken)
await payload.update({
collection: 'social-accounts',
id: account.id,
data: {
credentials: {
accessToken: newCredentials.access_token || undefined,
refreshToken: newCredentials.refresh_token || account.credentials.refreshToken,
tokenExpiresAt: newCredentials.expiry_date
? new Date(newCredentials.expiry_date).toISOString()
: undefined,
},
},
})
console.log(' ✅ Token refreshed')
} catch (refreshError) {
const msg = refreshError instanceof Error ? refreshError.message : 'Unknown error'
console.error(' ❌ Token refresh failed:', msg)
errors.push(`${account.displayName}: Token refresh failed - ${msg}`)
continue
}
}
// CommentsSyncService dynamisch importieren
const { CommentsSyncService } = await import(
'@/lib/integrations/youtube/CommentsSyncService'
)
const syncService = new CommentsSyncService(payload)
const result = await syncService.syncComments({
socialAccountId: account.id,
maxComments: 100,
analyzeWithAI: true,
})
const created = result.newComments || 0
const updated = result.updatedComments || 0
totalNew += created
totalUpdated += updated
successfulSyncs++
results.push({
accountId: account.id,
displayName: account.displayName || `Account ${account.id}`,
success: true,
created,
updated,
errors: result.errors || [],
})
console.log(` ✅ New: ${created}, Updated: ${updated}`)
if (result.errors?.length > 0) {
errors.push(...result.errors.map((e: string) => `${account.displayName}: ${e}`))
}
} catch (accountError: unknown) {
const msg = accountError instanceof Error ? accountError.message : 'Unknown error'
console.error(` ❌ Failed: ${msg}`)
errors.push(`${account.displayName}: ${msg}`)
results.push({
accountId: account.id,
displayName: account.displayName || `Account ${account.id}`,
success: false,
created: 0,
updated: 0,
errors: [msg],
})
}
}
// Zusammenfassung
console.log('\n' + '='.repeat(50))
console.log('📊 SYNC SUMMARY')
console.log('='.repeat(50))
console.log(` Accounts processed: ${accounts.docs.length}`)
console.log(` Successful syncs: ${successfulSyncs}`)
console.log(` New comments: ${totalNew}`)
console.log(` Updated: ${totalUpdated}`)
console.log(` Errors: ${errors.length}`)
if (errors.length > 0) {
console.log('\n⚠ Errors:')
errors.forEach((e) => console.log(` - ${e}`))
}
return {
totalAccounts: accounts.docs.length,
successfulSyncs,
totalNew,
totalUpdated,
errors,
}
}
/**
* Standalone-Ausführung für Cron-Job
*/
async function main() {
try {
const payload = await getPayload({ config })
await runSyncAllCommentsJob(payload)
process.exit(0)
} catch (error) {
console.error('❌ Sync job failed:', error)
process.exit(1)
}
}
// Wenn direkt ausgeführt (nicht importiert)
if (require.main === module) {
main()
}