235 lines
7.4 KiB
TypeScript
235 lines
7.4 KiB
TypeScript
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod'
|
|
import { z } from 'zod'
|
|
import { authMiddleware } from '@/middlewares/auth.middleware'
|
|
import { CodeStatsService } from '@/services/code-stats.service'
|
|
import {
|
|
createResponseSchema,
|
|
ErrorCode,
|
|
errorResponse,
|
|
errorResponseSchema,
|
|
successResponse,
|
|
} from '@/types/response'
|
|
|
|
// Schema for request parameters
|
|
const repoParamsSchema = z.object({
|
|
owner: z.string(),
|
|
repo: z.string(),
|
|
})
|
|
|
|
// Query schemas
|
|
const fileTreeQuerySchema = z.object({
|
|
branch: z.string().optional().default('main'),
|
|
recursive: z.coerce.boolean().optional().default(true),
|
|
})
|
|
|
|
const hotFilesQuerySchema = z.object({
|
|
limit: z.coerce.number().min(10).max(200).optional().default(100),
|
|
top: z.coerce.number().min(5).max(50).optional().default(20),
|
|
})
|
|
|
|
const fileStructureQuerySchema = z.object({
|
|
branch: z.string().optional().default('main'),
|
|
})
|
|
|
|
// Response schemas
|
|
const languageStatsSchema = z.record(z.string(), z.number())
|
|
|
|
const codeFrequencySchema = z.object({
|
|
week: z.number(),
|
|
additions: z.number(),
|
|
deletions: z.number(),
|
|
})
|
|
|
|
const fileTreeSchema = z.object({
|
|
path: z.string(),
|
|
mode: z.string(),
|
|
type: z.enum(['blob', 'tree']),
|
|
sha: z.string(),
|
|
size: z.number().optional(),
|
|
url: z.string().optional(),
|
|
})
|
|
|
|
const hotFileSchema = z.object({
|
|
path: z.string(),
|
|
changeCount: z.number(),
|
|
lastModified: z.string(),
|
|
authors: z.string().array(),
|
|
})
|
|
|
|
const fileStructureSummarySchema = z.object({
|
|
totalFiles: z.number(),
|
|
totalDirectories: z.number(),
|
|
totalSize: z.number(),
|
|
filesByExtension: z.record(z.string(), z.number()),
|
|
largestFiles: z
|
|
.object({
|
|
path: z.string(),
|
|
size: z.number(),
|
|
})
|
|
.array(),
|
|
})
|
|
|
|
// Response schemas with unified format
|
|
const languageStatsResponseSchema = createResponseSchema(languageStatsSchema)
|
|
const codeFrequencyResponseSchema = createResponseSchema(codeFrequencySchema.array())
|
|
const fileTreeResponseSchema = createResponseSchema(fileTreeSchema.array())
|
|
const hotFilesResponseSchema = createResponseSchema(hotFileSchema.array())
|
|
const fileStructureSummaryResponseSchema = createResponseSchema(fileStructureSummarySchema)
|
|
|
|
export const codeStatsRoutes: FastifyPluginAsyncZod = async (app) => {
|
|
// All routes in this plugin require authentication
|
|
app.addHook('preHandler', authMiddleware)
|
|
|
|
// Route to get language distribution
|
|
app.get(
|
|
'/:owner/:repo/languages',
|
|
{
|
|
schema: {
|
|
description: 'Get programming language distribution (in bytes)',
|
|
tags: ['code-stats'],
|
|
params: repoParamsSchema,
|
|
response: {
|
|
200: languageStatsResponseSchema,
|
|
500: errorResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
async (request, reply) => {
|
|
try {
|
|
const { owner, repo } = request.params
|
|
const { accessToken, username } = request.user
|
|
const codeStatsService = new CodeStatsService(accessToken, username)
|
|
const languages = await codeStatsService.getLanguageStats(owner, repo)
|
|
return successResponse(languages)
|
|
} catch (err) {
|
|
const error = err as Error
|
|
const errorMessage = error.message || 'Failed to fetch language statistics'
|
|
return reply.status(500).send(errorResponse(ErrorCode.GITHUB_API_ERROR, errorMessage))
|
|
}
|
|
}
|
|
)
|
|
|
|
// Route to get code frequency (weekly additions/deletions)
|
|
app.get(
|
|
'/:owner/:repo/code-frequency',
|
|
{
|
|
schema: {
|
|
description:
|
|
'Get code frequency statistics (weekly additions/deletions). ⚠️ Only for repos with <10k commits',
|
|
tags: ['code-stats'],
|
|
params: repoParamsSchema,
|
|
response: {
|
|
200: codeFrequencyResponseSchema,
|
|
500: errorResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
async (request, reply) => {
|
|
try {
|
|
const { owner, repo } = request.params
|
|
const { accessToken, username } = request.user
|
|
const codeStatsService = new CodeStatsService(accessToken, username)
|
|
const frequency = await codeStatsService.getCodeFrequency(owner, repo)
|
|
return successResponse(frequency)
|
|
} catch (err) {
|
|
const error = err as Error
|
|
const errorMessage = error.message || 'Failed to fetch code frequency'
|
|
return reply.status(500).send(errorResponse(ErrorCode.GITHUB_API_ERROR, errorMessage))
|
|
}
|
|
}
|
|
)
|
|
|
|
// Route to get file tree structure
|
|
app.get(
|
|
'/:owner/:repo/file-tree',
|
|
{
|
|
schema: {
|
|
description: 'Get repository file tree structure',
|
|
tags: ['code-stats'],
|
|
params: repoParamsSchema,
|
|
querystring: fileTreeQuerySchema,
|
|
response: {
|
|
200: fileTreeResponseSchema,
|
|
500: errorResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
async (request, reply) => {
|
|
try {
|
|
const { owner, repo } = request.params
|
|
const { branch, recursive } = request.query
|
|
const { accessToken, username } = request.user
|
|
const codeStatsService = new CodeStatsService(accessToken, username)
|
|
const fileTree = await codeStatsService.getFileTree(owner, repo, branch, recursive)
|
|
return successResponse(fileTree)
|
|
} catch (err) {
|
|
const error = err as Error
|
|
const errorMessage = error.message || 'Failed to fetch file tree'
|
|
return reply.status(500).send(errorResponse(ErrorCode.GITHUB_API_ERROR, errorMessage))
|
|
}
|
|
}
|
|
)
|
|
|
|
// Route to get hot files (most frequently modified)
|
|
app.get(
|
|
'/:owner/:repo/hot-files',
|
|
{
|
|
schema: {
|
|
description: 'Get hot files (most frequently modified files) by analyzing recent commits',
|
|
tags: ['code-stats'],
|
|
params: repoParamsSchema,
|
|
querystring: hotFilesQuerySchema,
|
|
response: {
|
|
200: hotFilesResponseSchema,
|
|
500: errorResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
async (request, reply) => {
|
|
try {
|
|
const { owner, repo } = request.params
|
|
const { limit, top } = request.query
|
|
const { accessToken, username } = request.user
|
|
const codeStatsService = new CodeStatsService(accessToken, username)
|
|
const hotFiles = await codeStatsService.getHotFiles(owner, repo, limit, top)
|
|
return successResponse(hotFiles)
|
|
} catch (err) {
|
|
const error = err as Error
|
|
const errorMessage = error.message || 'Failed to analyze hot files'
|
|
return reply.status(500).send(errorResponse(ErrorCode.GITHUB_API_ERROR, errorMessage))
|
|
}
|
|
}
|
|
)
|
|
|
|
// Route to get file structure summary
|
|
app.get(
|
|
'/:owner/:repo/file-structure-summary',
|
|
{
|
|
schema: {
|
|
description: 'Get file structure summary (counts by type, largest files, etc.)',
|
|
tags: ['code-stats'],
|
|
params: repoParamsSchema,
|
|
querystring: fileStructureQuerySchema,
|
|
response: {
|
|
200: fileStructureSummaryResponseSchema,
|
|
500: errorResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
async (request, reply) => {
|
|
try {
|
|
const { owner, repo } = request.params
|
|
const { branch } = request.query
|
|
const { accessToken, username } = request.user
|
|
const codeStatsService = new CodeStatsService(accessToken, username)
|
|
const summary = await codeStatsService.getFileStructureSummary(owner, repo, branch)
|
|
return successResponse(summary)
|
|
} catch (err) {
|
|
const error = err as Error
|
|
const errorMessage = error.message || 'Failed to fetch file structure summary'
|
|
return reply.status(500).send(errorResponse(ErrorCode.GITHUB_API_ERROR, errorMessage))
|
|
}
|
|
}
|
|
)
|
|
}
|