Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ jobs:
dump_dir: ${{ env.DATA_DIR }}/dump
db_path: ${{ env.DATA_DIR }}/db.sqlite

# Export leaderboard API endpoints (monthly, weekly, lifetime)
- name: Export Leaderboard API Endpoints
run: bun run pipeline export-leaderboard --output-dir=${{ env.DATA_DIR }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Copy yesterday's stats for all tracked repositories
run: |
YESTERDAY=$(date -d "yesterday" +'%Y-%m-%d')
Expand Down
98 changes: 87 additions & 11 deletions cli/analyze-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import { calculateDateRange } from "@/lib/date-utils";
// Load environment variables from .env file
loadEnv();

// Validate required environment variables
const requiredEnvVars = ["GITHUB_TOKEN", "OPENROUTER_API_KEY"];
const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]);

if (missingEnvVars.length > 0) {
console.error(
`Error: Missing required environment variables: ${missingEnvVars.join(
", ",
)}`,
);
process.exit(1);
// Helper to validate environment variables
function validateEnvVars(requiredVars: string[]) {
const missingVars = requiredVars.filter((envVar) => !process.env[envVar]);
if (missingVars.length > 0) {
console.error(
`Error: Missing required environment variables: ${missingVars.join(", ")}`,
);
process.exit(1);
}
}

import { Command } from "@commander-js/extra-typings";
Expand Down Expand Up @@ -76,6 +74,9 @@ program
false,
)
.action(async (options) => {
// Validate required environment variables for ingestion
validateEnvVars(["GITHUB_TOKEN"]);

try {
// Dynamically import the config
const configPath = join(import.meta.dir, options.config);
Expand Down Expand Up @@ -140,6 +141,9 @@ program
false,
)
.action(async (options) => {
// Validate required environment variables for processing
validateEnvVars(["GITHUB_TOKEN"]);

try {
// Dynamically import the config
const configPath = join(import.meta.dir, options.config);
Expand Down Expand Up @@ -200,6 +204,9 @@ program
.option("-d, --days <number>", "Number of days to look back from before date")
.option("--all", "Process all data since contributionStartDate", false)
.action(async (options) => {
// Validate required environment variables for export
validateEnvVars(["GITHUB_TOKEN"]);

try {
// Dynamically import the config
const configPath = join(import.meta.dir, options.config);
Expand Down Expand Up @@ -288,6 +295,8 @@ program
.option("--weekly", "Generate weekly summaries")
.option("--monthly", "Generate monthly summaries")
.action(async (options) => {
// Validate required environment variables for AI summaries
validateEnvVars(["GITHUB_TOKEN", "OPENROUTER_API_KEY"]);
try {
// Dynamically import the config
const configPath = join(import.meta.dir, options.config);
Expand Down Expand Up @@ -388,4 +397,71 @@ program
}
});

program
.command("export-leaderboard")
.description("Generate static JSON leaderboard API endpoints")
.option("-v, --verbose", "Enable verbose logging", false)
.option(
"-c, --config <path>",
"Path to pipeline config file",
DEFAULT_CONFIG_PATH,
)
.option("--output-dir <dir>", "Output directory for API files", "./data/")
.option(
"-l, --limit <number>",
"Limit number of users in leaderboard (0 = no limit)",
"100",
)
.action(async (options) => {
const logLevel: LogLevel = options.verbose ? "debug" : "info";
const rootLogger = createLogger({
minLevel: logLevel,
context: {
pipeline: "export-leaderboard",
},
});

try {
const { exportLeaderboardAPI } = await import(
"@/lib/pipelines/export/exportLeaderboardAPI"
);

const limit = parseInt(options.limit, 10);
const exportOptions = {
limit: limit > 0 ? limit : undefined,
logger: rootLogger,
};

rootLogger.info(chalk.cyan("\n Exporting Leaderboard API Endpoints"));
rootLogger.info(chalk.gray(`Output directory: ${options.outputDir}`));
if (exportOptions.limit) {
rootLogger.info(chalk.gray(`User limit: ${exportOptions.limit}`));
}

async function exportAllLeaderboardAPIs(
outputDir: string,
exportOpts: typeof exportOptions,
) {
const periods: Array<"monthly" | "weekly" | "lifetime"> = [
"monthly",
"weekly",
"lifetime",
];

for (const period of periods) {
await exportLeaderboardAPI(outputDir, period, exportOpts);
}
}

await exportAllLeaderboardAPIs(options.outputDir, exportOptions);

rootLogger.info(
chalk.green("\n Leaderboard API export completed successfully!"),
);
} catch (error: unknown) {
console.error(chalk.red("Error exporting leaderboard API:"), error);
process.exit(1);
}
});

program.parse(process.argv);
17 changes: 17 additions & 0 deletions src/__testing__/helpers/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
issueComments,
rawPullRequestFiles,
repositories,
walletAddresses,
} from "@/lib/data/schema";
import { toDateString } from "@/lib/date-utils";
import { UTCDate } from "@date-fns/utc";
Expand All @@ -21,6 +22,7 @@ type IssueComment = InferInsertModel<typeof issueComments>;
type PRReview = InferInsertModel<typeof schema.prReviews>;
type PRComment = InferInsertModel<typeof schema.prComments>;
type RawCommit = InferInsertModel<typeof schema.rawCommits>;
type WalletAddress = InferInsertModel<typeof walletAddresses>;

export function generateMockUsers(items: Partial<User>[]): User[] {
return items.map((overrides) => ({
Expand Down Expand Up @@ -250,3 +252,18 @@ export function generateMockRepoSummaries(
...overrides,
}));
}

export function generateMockWalletAddresses(
items: Partial<WalletAddress>[],
): WalletAddress[] {
return items.map((overrides) => ({
id: faker.number.int({ min: 1, max: 100000 }),
userId: overrides.userId ?? faker.internet.username(),
chainId: overrides.chainId ?? "eip155:1",
accountAddress: overrides.accountAddress ?? faker.finance.ethereumAddress(),
label: overrides.label ?? null,
isPrimary: overrides.isPrimary ?? false,
isActive: overrides.isActive ?? true,
...overrides,
}));
}
Loading