mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 10:36:07 +00:00
feat: add package pruning system with configurable grace periods
- Implemented automatic pruning of stale packages across all platform sync endpoints (Arch, AUR, Debian, Ubuntu, Fedora, Homebrew, Winget) - Added `last_seen_at` tracking to mark packages as seen during sync runs and identify inactive packages - Introduced `PRUNE_GRACE_DAYS` and `PRUNE_HARD_DELETE_DAYS` environment variables for configurable soft-delete and hard-delete thresholds
This commit is contained in:
@@ -24,3 +24,8 @@ SYNC_SERVER_ONLY=false
|
|||||||
AUTO_SYNC_DAYS=1
|
AUTO_SYNC_DAYS=1
|
||||||
# Secret key to authorize sync operations from server
|
# Secret key to authorize sync operations from server
|
||||||
SYNC_SECRET_KEY=your_sync_secret_key_here
|
SYNC_SECRET_KEY=your_sync_secret_key_here
|
||||||
|
|
||||||
|
# Prune Configuration
|
||||||
|
# Number of days to wait before pruning packages
|
||||||
|
PRUNE_GRACE_DAYS=7
|
||||||
|
PRUNE_HARD_DELETE_DAYS=1
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- Add last_seen_at to track soft-deletion by sync scope
|
||||||
|
ALTER TABLE packages
|
||||||
|
ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
-- Backfill existing rows so they are not immediately purged until their next scoped sync
|
||||||
|
UPDATE packages SET last_seen_at = NOW() WHERE last_seen_at IS NULL;
|
||||||
|
|
||||||
|
-- Helpful index for prune queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_packages_seen_scope
|
||||||
|
ON packages (platform_id, repository, is_active, last_seen_at);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { ArchPackageFetcher } from '@/services/archPackageFetcher'
|
import { ArchPackageFetcher } from '@/services/archPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -51,6 +52,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// Start sync in background
|
// Start sync in background
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
const fetcher = new ArchPackageFetcher()
|
const fetcher = new ArchPackageFetcher()
|
||||||
|
|
||||||
// Fetch packages
|
// Fetch packages
|
||||||
@@ -76,6 +78,31 @@ export async function POST(request: NextRequest) {
|
|||||||
syncStatus.storeTotal = total
|
syncStatus.storeTotal = total
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// Prune packages not seen in this run (Arch official scope)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository = $2
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $3)`,
|
||||||
|
['arch', 'official', cutoff]
|
||||||
|
)
|
||||||
|
// Optional hard delete of long-inactive packages
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository = $2
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $3`,
|
||||||
|
['arch', 'official', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
console.log('✅ Arch Linux sync completed successfully')
|
console.log('✅ Arch Linux sync completed successfully')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { AurPackageFetcher } from '@/services/aurPackageFetcher'
|
import { AurPackageFetcher } from '@/services/aurPackageFetcher'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -36,9 +37,35 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
const fetcher = new AurPackageFetcher()
|
const fetcher = new AurPackageFetcher()
|
||||||
const pkgs = await fetcher.fetchAllPackages()
|
const pkgs = await fetcher.fetchAllPackages()
|
||||||
await fetcher.storePackages(pkgs)
|
await fetcher.storePackages(pkgs)
|
||||||
|
// Prune not-seen AUR packages for this run
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository = $2
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $3)`,
|
||||||
|
['arch', 'aur', cutoff]
|
||||||
|
)
|
||||||
|
// Optional hard delete of long-inactive AUR packages
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository = $2
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $3`,
|
||||||
|
['arch', 'aur', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
syncStatus.status = 'error'
|
syncStatus.status = 'error'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { DebianPackageFetcher } from '@/services/debianPackageFetcher'
|
import { DebianPackageFetcher } from '@/services/debianPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -36,7 +37,30 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
await DebianPackageFetcher.fetchDebianPackages()
|
await DebianPackageFetcher.fetchDebianPackages()
|
||||||
|
// Prune not-seen Debian packages (platform debian)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $2)`,
|
||||||
|
['debian', cutoff]
|
||||||
|
)
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $2`,
|
||||||
|
['debian', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
syncStatus.status = 'error'
|
syncStatus.status = 'error'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { FedoraPackageFetcher } from '@/services/fedoraPackageFetcher'
|
import { FedoraPackageFetcher } from '@/services/fedoraPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -51,6 +52,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// Start sync in background
|
// Start sync in background
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
const fetcher = new FedoraPackageFetcher()
|
const fetcher = new FedoraPackageFetcher()
|
||||||
|
|
||||||
// Fetch packages
|
// Fetch packages
|
||||||
@@ -76,6 +78,30 @@ export async function POST(request: NextRequest) {
|
|||||||
syncStatus.storeTotal = total
|
syncStatus.storeTotal = total
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// Prune packages not seen in this run (Fedora; repository is NULL)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $2)`,
|
||||||
|
['fedora', cutoff]
|
||||||
|
)
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $2`,
|
||||||
|
['fedora', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
console.log('✅ Fedora sync completed successfully')
|
console.log('✅ Fedora sync completed successfully')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { HomebrewPackageFetcher } from '@/services/homebrewPackageFetcher'
|
import { HomebrewPackageFetcher } from '@/services/homebrewPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -51,6 +52,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// Start sync in background
|
// Start sync in background
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
const fetcher = new HomebrewPackageFetcher()
|
const fetcher = new HomebrewPackageFetcher()
|
||||||
|
|
||||||
// Fetch packages
|
// Fetch packages
|
||||||
@@ -76,6 +78,30 @@ export async function POST(request: NextRequest) {
|
|||||||
syncStatus.storeTotal = total
|
syncStatus.storeTotal = total
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// Prune not-seen Homebrew packages (platform macos, repository IS NULL)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $2)`,
|
||||||
|
['macos', cutoff]
|
||||||
|
)
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $2`,
|
||||||
|
['macos', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
console.log('✅ Homebrew sync completed successfully')
|
console.log('✅ Homebrew sync completed successfully')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { DebianPackageFetcher } from '@/services/debianPackageFetcher'
|
import { DebianPackageFetcher } from '@/services/debianPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -36,7 +37,30 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
await DebianPackageFetcher.fetchUbuntuPackages()
|
await DebianPackageFetcher.fetchUbuntuPackages()
|
||||||
|
// Prune not-seen Ubuntu packages (platform ubuntu)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $2)`,
|
||||||
|
['ubuntu', cutoff]
|
||||||
|
)
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $2`,
|
||||||
|
['ubuntu', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
syncStatus.status = 'error'
|
syncStatus.status = 'error'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { WingetPackageFetcher } from '@/services/wingetPackageFetcher'
|
import { WingetPackageFetcher } from '@/services/wingetPackageFetcher'
|
||||||
|
import { query } from '@/lib/database/config'
|
||||||
import { SyncAuth } from '@/lib/sync/auth'
|
import { SyncAuth } from '@/lib/sync/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -51,6 +52,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// Start sync in background
|
// Start sync in background
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
const runStartedAt = new Date()
|
||||||
const fetcher = new WingetPackageFetcher()
|
const fetcher = new WingetPackageFetcher()
|
||||||
|
|
||||||
// Fetch packages
|
// Fetch packages
|
||||||
@@ -76,6 +78,30 @@ export async function POST(request: NextRequest) {
|
|||||||
syncStatus.storeTotal = total
|
syncStatus.storeTotal = total
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// Prune not-seen Winget packages (platform windows, repository IS NULL)
|
||||||
|
const graceDays = parseInt(process.env.PRUNE_GRACE_DAYS || '0', 10)
|
||||||
|
const cutoff = new Date(runStartedAt.getTime() - (graceDays > 0 ? graceDays : 0) * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`UPDATE packages
|
||||||
|
SET is_active = false
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = true
|
||||||
|
AND (last_seen_at IS NULL OR last_seen_at < $2)`,
|
||||||
|
['windows', cutoff]
|
||||||
|
)
|
||||||
|
const hardDays = parseInt(process.env.PRUNE_HARD_DELETE_DAYS || '0', 10)
|
||||||
|
if (hardDays > 0) {
|
||||||
|
const deleteCutoff = new Date(runStartedAt.getTime() - hardDays * 24 * 60 * 60 * 1000)
|
||||||
|
await query(
|
||||||
|
`DELETE FROM packages
|
||||||
|
WHERE platform_id = $1
|
||||||
|
AND repository IS NULL
|
||||||
|
AND is_active = false
|
||||||
|
AND last_seen_at IS NOT NULL AND last_seen_at < $2`,
|
||||||
|
['windows', deleteCutoff]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
syncStatus.status = 'complete'
|
syncStatus.status = 'complete'
|
||||||
console.log('✅ Winget sync completed successfully')
|
console.log('✅ Winget sync completed successfully')
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ export class ArchPackageFetcher {
|
|||||||
if (existingResult.rows.length === 0) {
|
if (existingResult.rows.length === 0) {
|
||||||
// Insert new package (id will be auto-generated by SERIAL)
|
// Insert new package (id will be auto-generated by SERIAL)
|
||||||
await query(
|
await query(
|
||||||
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active)
|
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active, last_seen_at)
|
||||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8)`,
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
||||||
[pkg.name, pkg.description, pkg.version, 'arch', null, 'official', 0, true]
|
[pkg.name, pkg.description, pkg.version, 'arch', null, 'official', 0, true]
|
||||||
)
|
)
|
||||||
stored++
|
stored++
|
||||||
@@ -184,12 +184,17 @@ export class ArchPackageFetcher {
|
|||||||
// Update if version changed
|
// Update if version changed
|
||||||
await query(
|
await query(
|
||||||
`UPDATE packages
|
`UPDATE packages
|
||||||
SET version = $1, description = $2, repository = $3, updated_at = NOW()
|
SET version = $1, description = $2, repository = $3, last_seen_at = NOW(), is_active = true, updated_at = NOW()
|
||||||
WHERE id = $4`,
|
WHERE id = $4`,
|
||||||
[pkg.version, pkg.description, 'official', existingResult.rows[0].id]
|
[pkg.version, pkg.description, 'official', existingResult.rows[0].id]
|
||||||
)
|
)
|
||||||
updated++
|
updated++
|
||||||
} else {
|
} else {
|
||||||
|
// Still mark as seen to avoid pruning
|
||||||
|
await query(
|
||||||
|
`UPDATE packages SET last_seen_at = NOW(), is_active = true WHERE id = $1`,
|
||||||
|
[existingResult.rows[0].id]
|
||||||
|
)
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,18 +190,23 @@ export class AurPackageFetcher {
|
|||||||
)
|
)
|
||||||
if (existing.rows.length === 0) {
|
if (existing.rows.length === 0) {
|
||||||
await query(
|
await query(
|
||||||
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active)
|
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active, last_seen_at)
|
||||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8)`,
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
||||||
[pkg.name, pkg.description, pkg.version, 'arch', null, 'aur', 0, true]
|
[pkg.name, pkg.description, pkg.version, 'arch', null, 'aur', 0, true]
|
||||||
)
|
)
|
||||||
stored++
|
stored++
|
||||||
} else if (existing.rows[0].version !== pkg.version) {
|
} else if (existing.rows[0].version !== pkg.version) {
|
||||||
await query(
|
await query(
|
||||||
`UPDATE packages SET version = $1, description = $2, repository = $3, updated_at = NOW() WHERE id = $4`,
|
`UPDATE packages SET version = $1, description = $2, repository = $3, last_seen_at = NOW(), is_active = true, updated_at = NOW() WHERE id = $4`,
|
||||||
[pkg.version, pkg.description, 'aur', existing.rows[0].id]
|
[pkg.version, pkg.description, 'aur', existing.rows[0].id]
|
||||||
)
|
)
|
||||||
updated++
|
updated++
|
||||||
} else {
|
} else {
|
||||||
|
// Still mark as seen to avoid pruning
|
||||||
|
await query(
|
||||||
|
`UPDATE packages SET last_seen_at = NOW(), is_active = true WHERE id = $1`,
|
||||||
|
[existing.rows[0].id]
|
||||||
|
)
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
if (onProgress && (stored + updated + skipped) % 100 === 0) {
|
if (onProgress && (stored + updated + skipped) % 100 === 0) {
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ export class FedoraPackageFetcher {
|
|||||||
if (existingResult.rows.length === 0) {
|
if (existingResult.rows.length === 0) {
|
||||||
// Insert new package
|
// Insert new package
|
||||||
await query(
|
await query(
|
||||||
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active)
|
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active, last_seen_at)
|
||||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8)`,
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
||||||
[pkg.name, pkg.description, pkg.version, 'fedora', null, null, 0, true]
|
[pkg.name, pkg.description, pkg.version, 'fedora', null, null, 0, true]
|
||||||
)
|
)
|
||||||
stored++
|
stored++
|
||||||
@@ -153,12 +153,17 @@ export class FedoraPackageFetcher {
|
|||||||
// Update if version changed
|
// Update if version changed
|
||||||
await query(
|
await query(
|
||||||
`UPDATE packages
|
`UPDATE packages
|
||||||
SET version = $1, description = $2, repository = $3, updated_at = NOW()
|
SET version = $1, description = $2, repository = $3, last_seen_at = NOW(), is_active = true, updated_at = NOW()
|
||||||
WHERE id = $4`,
|
WHERE id = $4`,
|
||||||
[pkg.version, pkg.description, null, existingResult.rows[0].id]
|
[pkg.version, pkg.description, null, existingResult.rows[0].id]
|
||||||
)
|
)
|
||||||
updated++
|
updated++
|
||||||
} else {
|
} else {
|
||||||
|
// Still mark as seen to avoid pruning
|
||||||
|
await query(
|
||||||
|
`UPDATE packages SET last_seen_at = NOW(), is_active = true WHERE id = $1`,
|
||||||
|
[existingResult.rows[0].id]
|
||||||
|
)
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ export class HomebrewPackageFetcher {
|
|||||||
if (existingResult.rows.length === 0) {
|
if (existingResult.rows.length === 0) {
|
||||||
// Insert new package
|
// Insert new package
|
||||||
await query(
|
await query(
|
||||||
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active)
|
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active, last_seen_at)
|
||||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8)`,
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
||||||
[pkg.name, pkg.description, pkg.version, 'macos', null, null, 0, true]
|
[pkg.name, pkg.description, pkg.version, 'macos', null, null, 0, true]
|
||||||
)
|
)
|
||||||
stored++
|
stored++
|
||||||
@@ -152,12 +152,17 @@ export class HomebrewPackageFetcher {
|
|||||||
// Update if version changed
|
// Update if version changed
|
||||||
await query(
|
await query(
|
||||||
`UPDATE packages
|
`UPDATE packages
|
||||||
SET version = $1, description = $2, updated_at = NOW()
|
SET version = $1, description = $2, last_seen_at = NOW(), is_active = true, updated_at = NOW()
|
||||||
WHERE id = $3`,
|
WHERE id = $3`,
|
||||||
[pkg.version, pkg.description, existingResult.rows[0].id]
|
[pkg.version, pkg.description, existingResult.rows[0].id]
|
||||||
)
|
)
|
||||||
updated++
|
updated++
|
||||||
} else {
|
} else {
|
||||||
|
// Still mark as seen to avoid pruning
|
||||||
|
await query(
|
||||||
|
`UPDATE packages SET last_seen_at = NOW(), is_active = true WHERE id = $1`,
|
||||||
|
[existingResult.rows[0].id]
|
||||||
|
)
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,8 +210,8 @@ export class PackageService {
|
|||||||
`INSERT INTO packages (
|
`INSERT INTO packages (
|
||||||
id, name, description, version, platform_id, category_id,
|
id, name, description, version, platform_id, category_id,
|
||||||
license_id, type, repository, homepage_url, download_url,
|
license_id, type, repository, homepage_url, download_url,
|
||||||
popularity_score
|
popularity_score, last_seen_at
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW())
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[id, name, description, version, platform_id, category_id,
|
[id, name, description, version, platform_id, category_id,
|
||||||
license_id, type, repository, homepage_url, download_url,
|
license_id, type, repository, homepage_url, download_url,
|
||||||
@@ -238,6 +238,11 @@ export class PackageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always mark as seen and active on update
|
||||||
|
fields.push('last_seen_at = NOW()')
|
||||||
|
fields.push('is_active = true')
|
||||||
|
fields.push('updated_at = NOW()')
|
||||||
|
|
||||||
if (fields.length === 0) {
|
if (fields.length === 0) {
|
||||||
return this.getById(id)
|
return this.getById(id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ export class WingetPackageFetcher {
|
|||||||
if (existingResult.rows.length === 0) {
|
if (existingResult.rows.length === 0) {
|
||||||
// Insert new package
|
// Insert new package
|
||||||
await query(
|
await query(
|
||||||
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active)
|
`INSERT INTO packages (id, name, description, version, platform_id, type, repository, popularity_score, is_active, last_seen_at)
|
||||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8)`,
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
||||||
[pkg.name, pkg.description, pkg.version, 'windows', null, null, 0, true]
|
[pkg.name, pkg.description, pkg.version, 'windows', null, null, 0, true]
|
||||||
)
|
)
|
||||||
stored++
|
stored++
|
||||||
@@ -239,12 +239,17 @@ export class WingetPackageFetcher {
|
|||||||
// Update if version changed
|
// Update if version changed
|
||||||
await query(
|
await query(
|
||||||
`UPDATE packages
|
`UPDATE packages
|
||||||
SET version = $1, description = $2, updated_at = NOW()
|
SET version = $1, description = $2, last_seen_at = NOW(), is_active = true, updated_at = NOW()
|
||||||
WHERE id = $3`,
|
WHERE id = $3`,
|
||||||
[pkg.version, pkg.description, existingResult.rows[0].id]
|
[pkg.version, pkg.description, existingResult.rows[0].id]
|
||||||
)
|
)
|
||||||
updated++
|
updated++
|
||||||
} else {
|
} else {
|
||||||
|
// Still mark as seen to avoid pruning
|
||||||
|
await query(
|
||||||
|
`UPDATE packages SET last_seen_at = NOW(), is_active = true WHERE id = $1`,
|
||||||
|
[existingResult.rows[0].id]
|
||||||
|
)
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user