first commit
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-28 08:55:05 +07:00
commit 32a36cceff
444 files changed
+67186

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
const { query } = require('../database/db');
class AiInsight {
/**
* Get AI insight by cycle and kandang
* @param {string} cycleId - Cycle ID
* @param {string|null} kandangId - Kandang ID (null for all kandangs)
* @returns {Promise<Object|null>} Insight object or null if not found or expired
*/
static async getByCycleAndKandang(cycleId, kandangId = null) {
try {
const result = await query(
`SELECT * FROM ai_insights
WHERE cycle_id = $1
AND ($2::text IS NULL AND kandang_id IS NULL OR kandang_id = $2)
AND expires_at > NOW()
ORDER BY generated_at DESC
LIMIT 1`,
[cycleId, kandangId]
);
if (result.rows.length === 0) return null;
return this.formatInsight(result.rows[0]);
} catch (error) {
console.error('Error getting AI insight:', error);
throw error;
}
}
/**
* Create or update AI insight
* @param {Object} insightData - Insight data
* @returns {Promise<Object>} Created/updated insight
*/
static async upsert(insightData) {
try {
const { cycleId, kandangId, insightText, version = 'v1' } = insightData;
// Calculate expiry date (6 hours from now)
const expiresAt = new Date();
expiresAt.setHours(expiresAt.getHours() + 6);
// Check if insight already exists
const existing = await this.getByCycleAndKandang(cycleId, kandangId);
if (existing) {
// Update existing
const result = await query(
`UPDATE ai_insights
SET insight_text = $1, version = $2, generated_at = NOW(), expires_at = $3, updated_at = NOW()
WHERE id = $4
RETURNING *`,
[insightText, version, expiresAt, existing.id]
);
return this.formatInsight(result.rows[0]);
} else {
// Create new
const result = await query(
`INSERT INTO ai_insights (cycle_id, kandang_id, insight_text, version, generated_at, expires_at)
VALUES ($1, $2, $3, $4, NOW(), $5)
RETURNING *`,
[cycleId, kandangId, insightText, version, expiresAt]
);
return this.formatInsight(result.rows[0]);
}
} catch (error) {
console.error('Error upserting AI insight:', error);
throw error;
}
}
/**
* Delete expired insights
* @returns {Promise<number>} Number of deleted rows
*/
static async cleanupExpired() {
try {
const result = await query(
'DELETE FROM ai_insights WHERE expires_at < NOW()',
[]
);
return result.rowCount;
} catch (error) {
console.error('Error cleaning up expired insights:', error);
throw error;
}
}
/**
* Format database row to API response format
* @param {Object} row - Database row
* @returns {Object} Formatted insight
*/
static formatInsight(row) {
return {
id: row.id,
cycleId: row.cycle_id,
kandangId: row.kandang_id,
insightText: row.insight_text,
version: row.version,
generatedAt: row.generated_at,
expiresAt: row.expires_at,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
}
module.exports = AiInsight;