11import { v } from 'convex/values'
22import { internal } from './_generated/api'
33import type { Doc , Id } from './_generated/dataModel'
4- import { action , internalQuery } from './_generated/server'
4+ import { action , internalMutation , internalQuery } from './_generated/server'
55import { assertRole , requireUserFromAction } from './lib/access'
66
7- const MAX_BATCH_SIZE = 500
8- const DEFAULT_BATCH_SIZE = MAX_BATCH_SIZE
7+ const DEFAULT_BATCH_SIZE = 50
8+ const MAX_BATCH_SIZE = 200
9+ const SYNC_STATE_KEY = 'default'
910
1011type BackupPageItem =
1112 | {
@@ -29,6 +30,23 @@ type BackupPageResult = {
2930 isDone : boolean
3031}
3132
33+ type BackupSyncState = {
34+ cursor : string | null
35+ }
36+
37+ export type SyncGitHubBackupsResult = {
38+ stats : {
39+ skillsScanned : number
40+ skillsSkipped : number
41+ skillsBackedUp : number
42+ skillsMissingVersion : number
43+ skillsMissingOwner : number
44+ errors : number
45+ }
46+ cursor : string | null
47+ isDone : boolean
48+ }
49+
3250export const getGitHubBackupPageInternal = internalQuery ( {
3351 args : {
3452 cursor : v . optional ( v . string ( ) ) ,
@@ -72,7 +90,7 @@ export const getGitHubBackupPageInternal = internalQuery({
7290 slug : skill . slug ,
7391 displayName : skill . displayName ,
7492 version : version . version ,
75- ownerHandle : owner . handle ?? owner . name ?? owner . email ?? owner . _id ,
93+ ownerHandle : owner . handle ?? owner . _id ,
7694 files : version . files ,
7795 publishedAt : version . createdAt ,
7896 } )
@@ -82,20 +100,68 @@ export const getGitHubBackupPageInternal = internalQuery({
82100 } ,
83101} )
84102
85- export const syncGitHubBackups = action ( {
103+ export const getGitHubBackupSyncStateInternal = internalQuery ( {
104+ args : { } ,
105+ handler : async ( ctx ) : Promise < BackupSyncState > => {
106+ const state = await ctx . db
107+ . query ( 'githubBackupSyncState' )
108+ . withIndex ( 'by_key' , ( q ) => q . eq ( 'key' , SYNC_STATE_KEY ) )
109+ . unique ( )
110+ return { cursor : state ?. cursor ?? null }
111+ } ,
112+ } )
113+
114+ export const setGitHubBackupSyncStateInternal = internalMutation ( {
115+ args : {
116+ cursor : v . optional ( v . string ( ) ) ,
117+ } ,
118+ handler : async ( ctx , args ) => {
119+ const now = Date . now ( )
120+ const state = await ctx . db
121+ . query ( 'githubBackupSyncState' )
122+ . withIndex ( 'by_key' , ( q ) => q . eq ( 'key' , SYNC_STATE_KEY ) )
123+ . unique ( )
124+
125+ if ( ! state ) {
126+ await ctx . db . insert ( 'githubBackupSyncState' , {
127+ key : SYNC_STATE_KEY ,
128+ cursor : args . cursor ,
129+ updatedAt : now ,
130+ } )
131+ return { ok : true as const }
132+ }
133+
134+ await ctx . db . patch ( state . _id , {
135+ cursor : args . cursor ,
136+ updatedAt : now ,
137+ } )
138+
139+ return { ok : true as const }
140+ } ,
141+ } )
142+
143+ export const syncGitHubBackups : ReturnType < typeof action > = action ( {
86144 args : {
87145 dryRun : v . optional ( v . boolean ( ) ) ,
88146 batchSize : v . optional ( v . number ( ) ) ,
89147 maxBatches : v . optional ( v . number ( ) ) ,
148+ resetCursor : v . optional ( v . boolean ( ) ) ,
90149 } ,
91- handler : async ( ctx , args ) => {
150+ handler : async ( ctx , args ) : Promise < SyncGitHubBackupsResult > => {
92151 const { user } = await requireUserFromAction ( ctx )
93152 assertRole ( user , [ 'admin' ] )
153+
154+ if ( args . resetCursor && ! args . dryRun ) {
155+ await ctx . runMutation ( internal . githubBackups . setGitHubBackupSyncStateInternal , {
156+ cursor : undefined ,
157+ } )
158+ }
159+
94160 return ctx . runAction ( internal . githubBackupsNode . syncGitHubBackupsInternal , {
95161 dryRun : args . dryRun ,
96162 batchSize : args . batchSize ,
97163 maxBatches : args . maxBatches ,
98- } )
164+ } ) as Promise < SyncGitHubBackupsResult >
99165 } ,
100166} )
101167
0 commit comments