🌳
pt0/ipfsF/checkIpfsPinsAI.mts
7const gamesFixmfsCmd = 'ptnode pt0/gamesapp/binF/epDbSync.mjs fixmfs'
8const donateFixmfsCmd = 'ptnode pt0/donateapp/binF/epDbSync.mjs fixmfs'
10const getMfsCids = async (mfsRootPath: string): Promise<Set<string>> => {
11 const client = getIpfsClient()
12 const cids = new Set<string>()
13 try {
14 for await (const entry of client.files.ls(mfsRootPath)) {
15 if ((entry as any).type === 'file') {
16 cids.add((entry as any).cid.toString())
17 }
18 }
19 } catch {}
20 return cids
23const collectGamesAppDbCids = async ({db}: {db: any}): Promise<Map<string, string[]>> => {
24 const rows = await db('dbgames').select('game_id', 'game_state')
25 const dbCids = new Map<string, string[]>()
26 const addCid = (cid: string, gameId: string) => {
27 const arr = dbCids.get(cid) ?? []
28 if (!arr.includes(gameId)) arr.push(gameId)
29 dbCids.set(cid, arr)
30 }
31 for (const row of rows) {
32 const state = typeof row.game_state === 'string' ? JSON.parse(row.game_state) : row.game_state
33 const stacks = state?.hiddenState?.paperStacks ?? {}
34 for (const stack of Object.values(stacks) as any[]) {
35 for (const r of stack?.responses ?? []) {
36 if (r.lastIpfsCid) addCid(r.lastIpfsCid, row.game_id)
37 }
38 }
39 for (const action of state?.gameActionHistA ?? []) {
40 if (action.ipfsCid) addCid(action.ipfsCid, row.game_id)
41 }
42 }
43 return dbCids
46const collectDonateAppDbCids = async ({db}: {db: any}): Promise<Map<string, string[]>> => {
47 const rows = await db('dona_entries').whereNotNull('ipfs_cid').select('ipfs_cid', 'id')
48 const dbCids = new Map<string, string[]>()
49 for (const row of rows) {
50 const arr = dbCids.get(row.ipfs_cid) ?? []
51 arr.push(row.id)
52 dbCids.set(row.ipfs_cid, arr)
53 }
54 return dbCids
57export const checkGamesAppIpfsPins = async ({db, qsName}: {db: any, qsName: string}) => {
58 const {mfsRoot, kvKey} = deriveMfsParamsFromQs(qsName)
59 const mfsEntry = await db('generic_kvs').where({key: kvKey}).first()
60 if (!mfsEntry?.value) { betLog('checkGamesIpfsPinsSkip', {kvKey}); return }
62 const dbCids = await collectGamesAppDbCids({db})
63 const mfsCids = await getMfsCids(mfsRoot)
64 const orphanedCids = [...dbCids.keys()].filter(cid => !mfsCids.has(cid))
65 const orphanCount = orphanedCids.length
67 betLog('checkGamesIpfsPins', {total: dbCids.size, orphaned: orphanCount})
68 throwIf(() => orphanCount > 0, {
69 orphanedCids: orphanedCids.map(cid => ({cid: cid.slice(0, 8) + '...', gameIds: dbCids.get(cid)})),
70 fixmfsCmd: gamesFixmfsCmd
71 })
74export const checkDonateAppIpfsPins = async ({db, qsName}: {db: any, qsName: string}) => {
75 const {mfsRoot, kvKey} = deriveMfsParamsFromQs(qsName)
76 const mfsEntry = await db('generic_kvs').where({key: kvKey}).first()
77 if (!mfsEntry?.value) { betLog('checkDonateIpfsPinsSkip', {kvKey}); return }
79 const dbCids = await collectDonateAppDbCids({db})
80 const mfsCids = await getMfsCids(mfsRoot)
81 const orphanedCids = [...dbCids.keys()].filter(cid => !mfsCids.has(cid))
82 const orphanCount = orphanedCids.length
84 betLog('checkDonateIpfsPins', {total: dbCids.size, orphaned: orphanCount})
85 throwIf(() => orphanCount > 0, {
86 orphanedCids: orphanedCids.map(cid => ({cid, entryIds: dbCids.get(cid)})),
87 fixmfsCmd: donateFixmfsCmd
88 })
91export const findAndFixOrphanedCids = async ({db, qsName}: {db: any, qsName: string}) => {
92 const {mfsRoot, kvKey} = deriveMfsParamsFromQs(qsName)
93 const mfsEntry = await db('generic_kvs').where({key: kvKey}).first()
94 if (!mfsEntry?.value) throw new Error(`No MFS root for ${kvKey}`)
96 const mfsCids = await getMfsCids(mfsRoot)
97 const orphanedWithExt: Array<{cid: string, ext: string}> = []
99 try {
100 const rows = await db('dbgames').select('game_state')
101 for (const row of rows) {
102 const state = typeof row.game_state === 'string' ? JSON.parse(row.game_state) : row.game_state
103 const stacks = state?.hiddenState?.paperStacks ?? {}
104 for (const stack of Object.values(stacks) as any[]) {
105 for (const r of stack?.responses ?? []) {
106 if (r.lastIpfsCid && !mfsCids.has(r.lastIpfsCid) && !orphanedWithExt.some(o => o.cid === r.lastIpfsCid)) {
107 orphanedWithExt.push({cid: r.lastIpfsCid, ext: 'png'})
108 }
109 }
110 }
111 for (const action of state?.gameActionHistA ?? []) {
112 if (action.ipfsCid && !mfsCids.has(action.ipfsCid) && !orphanedWithExt.some(o => o.cid === action.ipfsCid)) {
113 orphanedWithExt.push({cid: action.ipfsCid, ext: 'png'})
114 }
115 }
116 }
117 } catch {}
119 try {
120 const rows = await db('dona_entries').whereNotNull('ipfs_cid').select('ipfs_cid', 'upload_mime_type')
121 for (const row of rows) {
122 if (row.ipfs_cid && !mfsCids.has(row.ipfs_cid) && !orphanedWithExt.some(o => o.cid === row.ipfs_cid)) {
123 const ext = row.upload_mime_type ? (getExtFromMime(row.upload_mime_type) || 'bin') : 'bin'
124 orphanedWithExt.push({cid: row.ipfs_cid, ext})
125 }
126 }
127 } catch {}
129 if (orphanedWithExt.length === 0) {
130 betLog('fixmfsNoOrphans')
131 return {fixed: 0, failed: 0}
132 }
134 const summary = {fixed: 0, failed: 0}
135 for (const {cid, ext} of orphanedWithExt) {
136 try {
137 await pinCid(cid)
138 await addToIpfsMfsRoot({cid, ext, mfsRoot, kvKey})
139 summary.fixed++
140 betLog('fixmfsFixed', {cid, ext})
141 } catch (err) {
142 summary.failed++
143 betLog('fixmfsFail', {cid, errMsg: (err as Error).message})
144 }
145 }
146 betLog('fixmfsResult', summary)
147 return summary