feat: optimize JSON sync with 1 API call + raw URLs

- Replace GitHub API calls (390+) with 1 API call + raw URL downloads
- Create GitHubJsonService for efficient JSON file syncing
- Reduce API rate limiting issues by 99.7%
- Add automatic page reload after successful sync
- Update tests to use new service
- Maintain same functionality with better performance

Performance improvement:
- Before: 390+ GitHub API calls (1 per JSON file)
- After: 1 GitHub API call + 389 raw URL downloads
- Raw URLs have no rate limits, making sync much more reliable
This commit is contained in:
Michel Roegl-Brunner
2025-09-15 14:30:07 +02:00
parent 92b2c0d5fc
commit 82dc4643de
392 changed files with 3323 additions and 1381 deletions

View File

@@ -19,9 +19,11 @@ vi.mock('~/server/lib/git', () => ({
},
}))
vi.mock('~/server/services/github', () => ({
githubService: {
vi.mock('~/server/services/githubJsonService', () => ({
githubJsonService: {
syncJsonFiles: vi.fn(),
getAllScripts: vi.fn(),
getScriptBySlug: vi.fn(),
},
}))
@@ -212,8 +214,8 @@ describe('scriptsRouter', () => {
it('should return script on success', async () => {
const mockScript = { name: 'Test Script', slug: 'test-script' }
const { localScriptsService } = await import('~/server/services/localScripts')
vi.mocked(localScriptsService.getScriptBySlug).mockResolvedValue(mockScript)
const { githubJsonService } = await import('~/server/services/githubJsonService')
vi.mocked(githubJsonService.getScriptBySlug).mockResolvedValue(mockScript)
const result = await caller.getScriptBySlug({ slug: 'test-script' })
@@ -224,8 +226,8 @@ describe('scriptsRouter', () => {
})
it('should return error when script not found', async () => {
const { localScriptsService } = await import('~/server/services/localScripts')
vi.mocked(localScriptsService.getScriptBySlug).mockResolvedValue(null)
const { githubJsonService } = await import('~/server/services/githubJsonService')
vi.mocked(githubJsonService.getScriptBySlug).mockResolvedValue(null)
const result = await caller.getScriptBySlug({ slug: 'nonexistent' })
@@ -239,35 +241,36 @@ describe('scriptsRouter', () => {
describe('resyncScripts', () => {
it('should resync scripts successfully', async () => {
const mockGitHubScripts = [
{ name: 'Script 1', slug: 'script-1' },
{ name: 'Script 2', slug: 'script-2' },
]
const { githubService } = await import('~/server/services/github')
const { localScriptsService } = await import('~/server/services/localScripts')
const { githubJsonService } = await import('~/server/services/githubJsonService')
vi.mocked(githubService.getAllScripts).mockResolvedValue(mockGitHubScripts)
vi.mocked(localScriptsService.saveScriptsFromGitHub).mockResolvedValue(undefined)
vi.mocked(githubJsonService.syncJsonFiles).mockResolvedValue({
success: true,
message: 'Successfully synced 2 scripts from GitHub using 1 API call + raw downloads',
count: 2
})
const result = await caller.resyncScripts()
expect(result).toEqual({
success: true,
message: 'Successfully synced 2 scripts from GitHub to local directory',
message: 'Successfully synced 2 scripts from GitHub using 1 API call + raw downloads',
count: 2,
})
})
it('should return error on failure', async () => {
const { githubService } = await import('~/server/services/github')
vi.mocked(githubService.getAllScripts).mockRejectedValue(new Error('GitHub error'))
const { githubJsonService } = await import('~/server/services/githubJsonService')
vi.mocked(githubJsonService.syncJsonFiles).mockResolvedValue({
success: false,
message: 'GitHub error',
count: 0
})
const result = await caller.resyncScripts()
expect(result).toEqual({
success: false,
error: 'GitHub error',
message: 'GitHub error',
count: 0,
})
})

View File

@@ -2,7 +2,7 @@ import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import { scriptManager } from "~/server/lib/scripts";
import { gitManager } from "~/server/lib/git";
import { githubService } from "~/server/services/github";
import { githubJsonService } from "~/server/services/githubJsonService";
import { localScriptsService } from "~/server/services/localScripts";
import { scriptDownloaderService } from "~/server/services/scriptDownloader";
@@ -97,11 +97,11 @@ export const scriptsRouter = createTRPCRouter({
}
}),
// Get all scripts from local directory
// Get all scripts from GitHub (1 API call + raw downloads)
getAllScripts: publicProcedure
.query(async () => {
try {
const scripts = await localScriptsService.getAllScripts();
const scripts = await githubJsonService.getAllScripts();
return { success: true, scripts };
} catch (error) {
return {
@@ -112,12 +112,12 @@ export const scriptsRouter = createTRPCRouter({
}
}),
// Get script by slug from local directory
// Get script by slug from GitHub (1 API call + raw downloads)
getScriptBySlug: publicProcedure
.input(z.object({ slug: z.string() }))
.query(async ({ input }) => {
try {
const script = await localScriptsService.getScriptBySlug(input.slug);
const script = await githubJsonService.getScriptBySlug(input.slug);
if (!script) {
return {
success: false,
@@ -135,20 +135,17 @@ export const scriptsRouter = createTRPCRouter({
}
}),
// Resync scripts from GitHub repo to local directory
// Resync scripts from GitHub (1 API call + raw downloads)
resyncScripts: publicProcedure
.mutation(async () => {
try {
// First, try to get scripts from GitHub
const githubScripts = await githubService.getAllScripts();
// Save scripts to local directory
await localScriptsService.saveScriptsFromGitHub(githubScripts);
// Sync JSON files using 1 API call + raw downloads
const result = await githubJsonService.syncJsonFiles();
return {
success: true,
message: `Successfully synced ${githubScripts.length} scripts from GitHub to local directory`,
count: githubScripts.length
success: result.success,
message: result.message,
count: result.count
};
} catch (error) {
console.error('Error in resyncScripts:', error);