Skip to content

Add Admin API to the backend + more docs and badges#1

Open
ajhalili2006 wants to merge 15 commits into
mainfrom
hackclub/more-hcb-badges
Open

Add Admin API to the backend + more docs and badges#1
ajhalili2006 wants to merge 15 commits into
mainfrom
hackclub/more-hcb-badges

Conversation

@ajhalili2006
Copy link
Copy Markdown
Member

@ajhalili2006 ajhalili2006 commented Oct 31, 2024

TODO Update this placeholder with some real description soon.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • GitHub-based admin authentication and admin endpoints; new GITHUB_TOKEN env var.
    • New HCB badge endpoints (balance, total raised, donate) and improved badge SVG generation.
    • Edge-cache support and a /hcb/balances redirect for improved performance.
  • Documentation

    • Major docs updates: README, CONTRIBUTING, self-hosting, API terms, badges, logos, and scripts.
  • Bug Fixes

    • Tighter badge style validation and improved error/404 badge handling.
  • Style

    • Formatting and tooling settings refinements for consistency.

…hed requests

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
oh god i forgot to do this for months on my sandbox VM wtfbbq?

anyways let sync it out to github then

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Also updated the secrets behind the scenes rn, alongside some OpenAPI metadata
chaos updates.

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
@ajhalili2006 ajhalili2006 self-assigned this Oct 31, 2024
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 31, 2024

📝 Walkthrough

Walkthrough

Adds GitHub token configuration and admin GitHub-auth endpoints, introduces token validation and caching, refactors logo/icon resolution to remote fetch + KV cache, expands badge endpoints and styles validation, and updates docs, tooling, and editor settings across the repo.

Changes

Cohort / File(s) Summary
Env & tooling
/.env, /.env.prod, /.tool-versions, /.gitignore
Adds GITHUB_TOKEN (encrypted) to envs; adds deno 2.0.4 to .tool-versions; broadens .gitignore to *.decrypted.
VS Code config
.vscode/extensions.json, .vscode/settings.json
Formatting and settings additions (conventional commit scopes, editor.formatOnSave/Paste/Type flags, dotenv setting).
API: admin & auth
api/admin.ts, lib/githubAuth.ts, lib/config.ts, main.ts
New admin routes (auth test, grant/revoke placeholders) and GitHub auth module; token validation added to config; bearer auth wired into routes with KV-based caching.
Badges & SVGs
api/badges.ts, lib/utils.ts, lib/logos.ts, lib/db.ts, lib/cli/kv-seed.json
Adds HCB badge endpoints, style validation, cache headers; logos refactored to remote simple-icons fetch + KV cache; DB icon resolution updated; seed data adjusted (hcb-light/hcb-dark).
Metadata & OpenAPI
lib/metadata.ts, api/meta.ts, deno.json
Adds admin and meta tags, updates ping schema (override/tags), pins @octokit/rest version and removes empty deploy include.
Docs & README
README.md, docs/* (README.md, api-terms.md, hackclub-badges.md, logos.md, self-hosting.md, deno-scripts.md, CONTRIBUTING.md)
Extensive documentation additions and restructuring: contributing guide, deno scripts, self-hosting clarifications, API terms updates, and new/renamed logo docs.
CLI / utils / scripts
lib/cli/logger.ts, utils/* (badges-admin.ts, kv-keys-debugger.ts, manage-api-users.ts, preseed-kv.ts, types.ts)
Formatting, logging, and flow improvements; KV preseed and management scripts updated; new BadgeDataDbstore type added.
Main entry
main.ts
Adds CORS/enhanced middleware, edge-cache helper/flags, new /hcb routes and auth-protected admin endpoints, explicit Context typing.
Minor modules
lib/hcb.ts, lib/config.ts (other small edits)
Small doc/comment tweaks and added config fields (cacheNamespace, flags.edgeCache) plus getValidatedGithubToken export.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant GitHub
    participant KV
    Client->>API: Request protected admin endpoint (Bearer token)
    API->>KV: Check cached token hash
    alt Cache miss or expired
        API->>GitHub: Validate token / fetch user & team membership
        GitHub-->>API: User data + membership
        API->>KV: Store user data with TTL
    end
    API-->>Client: Return auth result (allowed / denied)
Loading

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 I nibble keys and hop through code,
Tokens tucked in burrows, safe and stowed.
Badges bloom bright, logos fetched and cached,
Docs and routes pruned—no detail trashed.
A tiny hop for developers, a joyous load.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main changes: adding an Admin API to the backend and expanding documentation and badge functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hackclub/more-hcb-badges
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.

OpenGrep is compatible with Semgrep configurations. Add an opengrep.yml or semgrep.yml configuration file to your project to enable OpenGrep analysis.

@ajhalili2006
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 31, 2024

✅ Actions performed

Full review triggered.

@ajhalili2006
Copy link
Copy Markdown
Member Author

@coderabbitai configuration

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 31, 2024

CodeRabbit configuration
language: en-US
tone_instructions: ''
early_access: true
enable_free_tier: true
reviews:
  profile: chill
  request_changes_workflow: true
  high_level_summary: true
  high_level_summary_placeholder: '@coderabbitai summary'
  auto_title_placeholder: '@coderabbitai'
  review_status: true
  commit_status: true
  poem: true
  collapse_walkthrough: true
  sequence_diagrams: true
  changed_files_summary: true
  labeling_instructions: []
  path_filters: []
  path_instructions: []
  abort_on_close: true
  auto_review:
    enabled: true
    auto_incremental_review: true
    ignore_title_keywords: []
    labels: []
    drafts: false
    base_branches: []
  tools:
    shellcheck:
      enabled: true
    ruff:
      enabled: true
    markdownlint:
      enabled: true
    github-checks:
      enabled: true
      timeout_ms: 90000
    languagetool:
      enabled: true
      enabled_only: false
      level: default
    biome:
      enabled: true
    hadolint:
      enabled: true
    swiftlint:
      enabled: true
    phpstan:
      enabled: true
      level: default
    golangci-lint:
      enabled: true
    yamllint:
      enabled: true
    gitleaks:
      enabled: true
    checkov:
      enabled: true
    detekt:
      enabled: true
    eslint:
      enabled: true
    rubocop:
      enabled: true
    buf:
      enabled: true
    regal:
      enabled: true
    actionlint:
      enabled: true
    pmd:
      enabled: true
    cppcheck:
      enabled: true
    semgrep:
      enabled: true
    circleci:
      enabled: true
chat:
  auto_reply: true
knowledge_base:
  opt_out: false
  learnings:
    scope: auto
  issues:
    scope: auto
  jira:
    project_keys: []
  linear:
    team_keys: []
  pull_requests:
    scope: auto

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 42

🧹 Outside diff range and nitpick comments (30)
utils/types.ts (1)

Line range hint 2-12: LGTM! Consider enhancing the documentation.

The formatting changes look good. However, since this interface is copied from badge-maker, it would be helpful to add:

  1. A link to the original source
  2. A brief description of each property's purpose

Example documentation improvement:

/**
 * Badge format configuration interface
 * @see https://github.com/badges/shields/blob/master/badge-maker/types.ts
 * 
 * @property label - Optional text shown on the left side of the badge
 * @property message - Required text shown on the right side of the badge
 * @property labelColor - Optional color for the left side (label)
 * @property color - Optional color for the right side (message)
 * @property style - Optional badge style preset
 * @property logoBase64 - Optional base64-encoded logo image
 * @property links - Optional array of URLs for left and right side click actions
 */
docs/README.md (1)

3-6: Consider adding Admin API documentation.

Given that this PR introduces Admin API functionality to the backend, consider adding a dedicated section or link for Admin API documentation to help developers understand the new administrative capabilities.

Example addition:

 - [API Docs, as generated by chanfana and our OpenAPI spec](https://badges.api.lorebooks.wiki/docs)
 - [API Terms and Privacy](./api-terms.md)
 - [(unofficial) Hack Club badges](./hackclub-badges.md)
 - [Available logos and badge logo admin](./logos.md)
+- [Admin API Documentation](./admin-api.md)
.vscode/settings.json (1)

18-21: Consider documenting the rationale for editor settings.

While the editor settings are valid, it would be helpful to document why these specific limits and formatting behaviors were chosen, especially the high colorDecoratorsLimit value of 50000.

Consider adding a JSON comment block above these settings explaining the rationale:

  "conventionalCommits.emojiFormat": "code",
+ // Increased color decorator limit to handle large files
  "editor.colorDecoratorsLimit": 50000,
+ // Enable automatic formatting for consistency
  "editor.formatOnPaste": true,
  "editor.formatOnSave": true,
  "editor.formatOnType": false,
lib/utils.ts (1)

2-7: Enhance JSDoc documentation with TypeScript annotations.

The documentation should include type information and be more specific about the return value.

 /**
  * Validate badge style name received from the HTTP request before
  * rendering into makeBadge function.
- * @param style String of a style name to validate against
- * @returns Either the lowercased style name or the defaults
+ * @param {string} [style] - Badge style name to validate
+ * @returns {string} The validated style name (lowercase) or "flat" as default
  */
utils/kv-keys-debugger.ts (1)

Line range hint 1-19: Consider adding error handling and type safety.

As this is a debugging utility, it would be beneficial to:

  1. Add try-catch blocks around KV operations
  2. Add type definitions for the entry values
  3. Consider masking sensitive data in the debug output

Here's a suggested implementation:

 import { config } from "../lib/config.ts";
 import { kv } from "../lib/db.ts";
+
+// Define types for your KV entries
+interface BadgeEntry {
+  name: string;
+  color: string;
+  // add other badge properties
+}
 
-console.log(`kvUrl - ${config.kvUrl} | env - ${Deno.env.get("DENO_KV_URL")}`);
-const kvApi = await kv(config.kvUrl);
+try {
+  console.log(`kvUrl - ${config.kvUrl} | env - ${Deno.env.get("DENO_KV_URL")}`);
+  const kvApi = await kv(config.kvUrl);
 
-const badges = await kvApi.list({ prefix: ["staticBadges"] });
-const logos = await kvApi.list({ prefix: ["badgeIcons"] });
+  const badges = await kvApi.list<BadgeEntry>({ prefix: ["staticBadges"] });
+  const logos = await kvApi.list({ prefix: ["badgeIcons"] });
 
-for await (const entry of badges) {
-  console.log(
-    `badge namespace: ${entry.key[1]}, badge name: ${entry.key[2]}, data: ${
-      JSON.stringify(entry.value)
-    }, stamp: ${entry.versionstamp}`,
-  );
-}
+  for await (const entry of badges) {
+    // Mask sensitive data if needed
+    const sanitizedValue = { ...entry.value, sensitiveField: '[REDACTED]' };
+    console.log(
+      `badge namespace: ${entry.key[1]}, badge name: ${entry.key[2]}, data: ${
+        JSON.stringify(sanitizedValue)
+      }, stamp: ${entry.versionstamp}`,
+    );
+  }
 
-for await (const entry of logos) {
-  console.log(`logo: ${entry.key[1]}, stamp: ${entry.versionstamp}`);
+  for await (const entry of logos) {
+    console.log(`logo: ${entry.key[1]}, stamp: ${entry.versionstamp}`);
+  }
+} catch (error) {
+  console.error('Error while debugging KV store:', error);
+  Deno.exit(1);
 }
api/meta.ts (1)

15-15: Consider simplifying the default value specification.

The default value is specified twice: once in Bool({ default: true }) and again in .default(true). While this works, it's redundant.

You could simplify it to either:

- ok: Bool({ default: true }).default(true),
+ ok: Bool({ default: true }),

or

- ok: Bool({ default: true }).default(true),
+ ok: Bool().default(true),
lib/config.ts (2)

1-4: Enhance documentation with detailed JSDoc comments.

The current documentation could be more comprehensive. Consider adding:

  • Description of each configuration option
  • Type information
  • Environment variable references
 /**
  * @module
- * @description Configuration
+ * @description Application configuration loaded from environment variables
+ * 
+ * @property {number} port - Server port (env: PORT, default: 8080)
+ * @property {string} homepage - API homepage URL (env: BADGES_API_HOMEPAGE)
+ * @property {string} kvUrl - Deno KV URL (env: DENO_KV_URL)
+ * @property {Object} github - GitHub-related configuration
+ * @property {string} github.authServiceToken - GitHub API token (env: GITHUB_TOKEN)
+ * @property {string} github.org - GitHub organization (env: GITHUB_ORG)
+ * @property {string} github.team_slug - Admin team slug (env: GITHUB_TEAM_ADMINS)
+ * @property {string} cacheNamespace - Cache namespace (env: DENO_CACHE_NAMESPACE)
+ * @property {Object} flags - Feature flags
+ * @property {string} flags.edgeCache - Edge caching flag (env: FF_DENO_EDGE_CACHE)
  */

4-20: Consider using a configuration management library.

The current configuration setup could benefit from:

  • TypeScript interfaces for type safety
  • Runtime validation using a schema validator (e.g., Zod)
  • Environment-specific configuration handling

Example implementation:

import { z } from "https://deno.land/x/zod/mod.ts";

const ConfigSchema = z.object({
  port: z.number().default(8080),
  homepage: z.string().url(),
  kvUrl: z.string().optional(),
  github: z.object({
    authServiceToken: z.string().min(1),
    org: z.string().default("lorebooks-wiki"),
    team_slug: z.string().default("api-admins"),
  }),
  cacheNamespace: z.string(),
  flags: z.object({
    edgeCache: z.boolean().default(false),
  }),
});

type Config = z.infer<typeof ConfigSchema>;

export const config = ConfigSchema.parse({
  // ... your current config
});
.env (1)

10-10: Document the required GitHub token permissions.

The secure implementation using encrypted values is good. However, please document the required GitHub token permissions and scope in a comment above the GITHUB_TOKEN variable. This helps other developers create tokens with the minimum necessary permissions.

Add a comment like this above the token:

+# GitHub Personal Access Token (Classic)
+# Required permissions: read:org, repo (for private repos)
 GITHUB_TOKEN="encrypted:..."
.env.prod (1)

9-9: Consider documenting the GITHUB_TOKEN's purpose and required permissions.

While the token is properly encrypted, it would be helpful to add a comment describing:

  • The intended use of this token
  • Required GitHub permissions/scopes
  • Any rate limiting considerations
+# GitHub Personal Access Token for admin API authentication
+# Required permissions: TBD
 GITHUB_TOKEN="encrypted:BIUCNVVVwehMW3+hmvN+SszVo+8pQB3wu/Gg1csjgcbf1XlEhe5ZTFdsUtzcZQFuJC5hd9wmGRrg71xPMP5ntrpUVmItps9mB+OdMEBmQPtexZjHBE1z6Ti2+oBYE4ghVszJV6TSnjRCydAEs0sNKM3FtPsPHd0xQlqt0xgV3WUdDZVpqPkn6AsJETjUrvEpvUcsoe+aF+KnpFjR39EWmJVUHJYiwmjlZXAmuAh5Z/0xql5BAr1Dv6d8S6HYXA=="
api/admin.ts (1)

8-8: Rename class to follow naming conventions.

The class name testGitHubAuth suggests this might be a test implementation. If this is a production endpoint, consider renaming it to something like GitHubAuthRoute or CheckGitHubAuthRoute.

lib/metadata.ts (1)

17-20: Add period for consistency in meta tag description.

Other tag descriptions end with periods, maintain consistency.

Apply this diff:

   {
     name: "meta",
-    description: "Service meta endpoints"
+    description: "Service meta endpoints."
   }
docs/api-terms.md (4)

Line range hint 1-4: Fix typo in the introduction.

There's a typo in the introduction: "tp" should be "to".

-By using the Badges API, you agree tp and understand the following:
+By using the Badges API, you agree to and understand the following:
🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: There is an agreement error between ‘agree’ and ‘tp’. Insert ‘a(n)’ or change the noun to plural.
Context: ...ard Time) By using the Badges API, you agree tp and understand the following: - You fo...

(PRP_VB_NN)


[uncategorized] ~10-~10: Possible missing comma found.
Context: ...point(s) for more than 5 requests per minute and respect the cache headers. - The AP...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~16-~16: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...ablize things at any point in the future and you also agree to any revisions we may ...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~23-~23: A comma may be missing after the conjunctive/linking adverb ‘Currently’.
Context: ...ooks.wiki/legal/tos ## Privacy Policy Currently we do not collect IP addresses from eac...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)


9-10: Enhance rate limit clause clarity.

Consider adding specificity about the consequences of violating the rate limit and how the limit is enforced.

-for more than 5 requests per minute and respect the cache headers.
+for more than 5 requests per minute and respect the cache headers. Exceeding this limit may result in temporary IP-based restrictions.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~10-~10: Possible missing comma found.
Context: ...point(s) for more than 5 requests per minute and respect the cache headers. - The AP...

(AI_HYDRA_LEO_MISSING_COMMA)


36-37: Clarify issue reporting location.

The term "here" is ambiguous. Consider providing a direct link to the GitHub repository's issues page.

-squad@crew.recaptime.dev` or file a new issue here.
+squad@crew.recaptime.dev` or file a new issue in our [GitHub repository](https://github.com/lorebooks-wiki/badges-api/issues).

10-10: Improve readability with proper comma usage.

Consider adding commas in the following locations for better readability:

  • After "point(s)" in the rate limit clause
  • Before "and" in the future changes clause
  • After "Currently" in the privacy policy
-point(s) for more than 5 requests
+point(s), for more than 5 requests

-future and you also agree
+future, and you also agree

-Currently we do not collect
+Currently, we do not collect

Also applies to: 16-16, 24-24

🧰 Tools
🪛 LanguageTool

[uncategorized] ~10-~10: Possible missing comma found.
Context: ...point(s) for more than 5 requests per minute and respect the cache headers. - The AP...

(AI_HYDRA_LEO_MISSING_COMMA)

utils/preseed-kv.ts (1)

1-43: Consider adding transaction-like behavior and progress tracking.

The current implementation could benefit from:

  1. Progress tracking to resume from failures
  2. Atomic operations to ensure data consistency
  3. Cleanup mechanism for partial failures
  4. Batch processing for better performance

This would make the script more reliable for large datasets and provide better visibility into the seeding process.

Would you like me to provide an example implementation with these improvements?

lib/db.ts (2)

67-67: LGTM with a suggestion for type safety

The trailing comma addition maintains consistency. While the current implementation is solid, consider enhancing type safety by using a const assertion for the badge types.

Consider defining the badge types as a const:

export const BADGE_TYPES = ['redirect', 'badge'] as const;
export type BadgeType = typeof BADGE_TYPES[number];

Then update the function signature:

export async function setBadgeData(
  project: string,
  badgeName: string,
  type: BadgeType,
  data: BadgeData,
)

This would provide better type safety and autocompletion support.


Line range hint 3-11: Consider implementing connection pooling and stronger error typing

The current implementation creates a new KV connection for each operation. While this works, it could be optimized for performance at scale.

Consider these improvements:

  1. Implement connection pooling to reuse KV connections
  2. Add specific error types instead of using generic string | object
  3. Validate the KV URL format before attempting connection
  4. Add connection timeout handling

Example error type improvement:

export type DbError = {
  code: 'CONNECTION_ERROR' | 'NOT_FOUND' | 'INVALID_DATA';
  message: string;
  details?: unknown;
};
docs/logos.md (4)

5-6: Consider adding a tracking link for the simple-icons integration.

To help users track progress, consider adding a link to the relevant GitHub issue or project task.

-Looking for those available on `simple-icons` npm package? We're working on
-integrating them soon.
+Looking for those available on `simple-icons` npm package? We're working on
+integrating them soon (track progress in [#issue-number]).

20-21: Improve grammar and clarity in the instructions.

The word "clipboard" needs proper article usage.

-2. Run `cat path/to/file | base64 --wrap=0` and copy the encoded string to
-   clipboard.
+2. Run `cat path/to/file | base64 --wrap=0` and copy the encoded string to the
+   clipboard.
🧰 Tools
🪛 LanguageTool

[grammar] ~20-~20: The word ‘clipboard’ is not correct in this context. Use one of the suggestions or replace it with an appropriate verb.
Context: ...4 --wrap=0 and copy the encoded string to clipboard. 3. Open the [lib/cli/kv-seed.json`](....

(VB_TO_NN_DT)


19-31: Consider adding example commands.

To make the instructions more user-friendly, consider adding an example command with a sample file path.

 1. Make sure the image or SVG in question is downloaded locally.
 2. Run `cat path/to/file | base64 --wrap=0` and copy the encoded string to
    clipboard.
+   Example: `cat assets/logos/example-logo.svg | base64 --wrap=0`
🧰 Tools
🪛 LanguageTool

[grammar] ~20-~20: The word ‘clipboard’ is not correct in this context. Use one of the suggestions or replace it with an appropriate verb.
Context: ...4 --wrap=0 and copy the encoded string to clipboard. 3. Open the [lib/cli/kv-seed.json`](....

(VB_TO_NN_DT)


40-41: Fix grammar in CLI instructions.

The word "clipboard" needs proper article usage.

-2. Run `cat path/to/file | base64 --wrap=0` and copy the encoded string to
-   clipboard.
+2. Run `cat path/to/file | base64 --wrap=0` and copy the encoded string to the
+   clipboard.
🧰 Tools
🪛 LanguageTool

[grammar] ~40-~40: The word ‘clipboard’ is not correct in this context. Use one of the suggestions or replace it with an appropriate verb.
Context: ...4 --wrap=0and copy the encoded string to clipboard. 3. Run dotenvx run -f .env.prod --...

(VB_TO_NN_DT)

lib/cli/logger.ts (1)

Line range hint 1-54: Consider architectural improvements for better type safety.

The codebase could benefit from several TypeScript improvements:

  1. Replace the levels object with a proper TypeScript enum
  2. Add proper type definitions instead of using @ts-expect-error
  3. Implement proper type safety for the setLogLevel function

Here's an example of how to improve the types:

enum LogLevel {
  ERROR = 0,
  WARN = 1,
  INFO = 2,
  VERBOSE = 4,
  DEBUG = 5,
  SILLY = 6
}

interface LogOptions {
  debug?: boolean;
  verbose?: boolean;
  quiet?: boolean;
  logLevel?: keyof typeof LogLevel;
}

// Then use these types in your functions
export function setLogLevel(options: LogOptions): void {
  // ... implementation
}

Would you like me to provide a complete type-safe implementation for the logging system?

docs/hackclub-badges.md (3)

3-5: Minor grammar improvements needed.

Consider these refinements:

  • Add "the" before "Hack Club Slack community"
  • Remove the hyphen in "fiscally hosted" as it's redundant with the -ly ending
-These badges are maintained by @ajhalili2006 for the benefit of Hack Club Slack
+These badges are maintained by @ajhalili2006 for the benefit of the Hack Club Slack
 community, especially those who participated during Arcade 2024 and open-source
-projects fiscally-hosted under HCB.
+projects fiscally hosted under HCB.
🧰 Tools
🪛 LanguageTool

[style] ~3-~3: ‘for the benefit’ might be wordy. Consider a shorter alternative.
Context: ... badges are maintained by @ajhalili2006 for the benefit of Hack Club Slack community, especiall...

(EN_WORDINESS_PREMIUM_FOR_THE_BENEFIT)


[uncategorized] ~3-~3: You might be missing the article “the” here.
Context: ...ned by @ajhalili2006 for the benefit of Hack Club Slack community, especially those ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~5-~5: The hyphen in fiscally-hosted is redundant.
Context: ...ng Arcade 2024 and open-source projects fiscally-hosted under HCB. ## Hack Clubber? If you're...

(ADVERB_LY_HYPHEN_FIX)


61-63: Minor grammar improvements needed in the introduction.

Consider these refinements:

  • Add "the" before "hackclub-arcade tag"
  • Add a comma after "tag"
 Built a project during [Arcade 2024](https://hackclub.com/arcade), including
 [Power Hour: Arcade](https://hackclub.com/arcade/power-hour)? Use this badge,
-alongside adding `hackclub-arcade` tag to your repository for discoverability.
+alongside adding the `hackclub-arcade` tag, to your repository for discoverability.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...hour)? Use this badge, alongside adding hackclub-arcade tag to your repository ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: Possible missing comma found.
Context: ...dge, alongside adding hackclub-arcade tag to your repository for discoverability....

(AI_HYDRA_LEO_MISSING_COMMA)


126-128: Maintain consistent hyphenation throughout the document.

Remove the redundant hyphens in "fiscally-hosted" across these sections to match the earlier correction.

-For organizations fiscally-hosted through HCB, you can use the Donate on HCB to
+For organizations fiscally hosted through HCB, you can use the Donate on HCB to
 add a donation link for your org or event. Just override the org slug by adding
 `org` query parameter pointing to your org.

-For organizations fiscally-hosted through HCB, you can also show your balances
+For organizations fiscally hosted through HCB, you can also show your balances
 as a badge while linking to your organization's public ledger on the platform.
 Just override the org slug by adding `org` query parameter pointing to your org.

Also applies to: 135-137

🧰 Tools
🪛 LanguageTool

[uncategorized] ~126-~126: The hyphen in fiscally-hosted is redundant.
Context: ...`` ## Donate on HCB For organizations fiscally-hosted through HCB, you can use the Donate on ...

(ADVERB_LY_HYPHEN_FIX)

utils/manage-api-users.ts (1)

Line range hint 1-173: Add rate limit handling and input validation

Consider implementing these security improvements:

  1. Add rate limit handling with exponential backoff
  2. Validate username input parameters
  3. Add confirmation prompts for destructive operations

Would you like me to provide an implementation for these security improvements?

🧰 Tools
🪛 Biome

[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

utils/badges-admin.ts (2)

Line range hint 89-152: Add input validation and consistent error handling in badges:add command.

The command needs more robust input validation and consistent error handling:

  1. Missing validation for project and name parameters
  2. No URL validation for redirect type
  3. Inconsistent error handling patterns
 .action(async (project, name, options) => {
+    // Validate project and name
+    if (!/^[a-z0-9-]+$/.test(project) || !/^[a-z0-9-]+$/.test(name)) {
+      logger.error('Project and name must contain only lowercase letters, numbers, and hyphens');
+      Deno.exit(1);
+    }
+
     logger.debug(`options ${JSON.stringify(options)}`);
     if (options.type == "redirect") {
-      if (options.url == undefined) {
-        logger.error(`you forgot to set --url option`);
+      if (!options.url) {
+        logger.error('Missing required --url option for redirect type');
+        Deno.exit(1);
+      }
+      
+      try {
+        new URL(options.url); // Validate URL format
+      } catch {
+        logger.error('Invalid URL format');
+        Deno.exit(1);
       }

Based on the analysis of the codebase, I can now generate the final response:

Security measures are partially implemented but need enhancement

The codebase has some security measures in place:

  • Style validation through validateBadgeStyle function that restricts to known safe values
  • SVG content type validation for uploaded icons
  • Badge generation through the trusted badge-maker package

However, several security concerns remain:

  • No input sanitization for badge text content (label, message)
  • No validation of redirect URLs
  • No rate limiting for KV operations
  • No validation of base64-encoded logo data beyond basic prefix checks
🔗 Analysis chain

Line range hint 1-193: Security: Add input sanitization and rate limiting.

Consider implementing these security measures:

  1. Sanitize all user inputs (especially badge parameters) to prevent injection attacks
  2. Implement rate limiting for KV operations
  3. Validate SVG content before storage to prevent XSS

Let's check for any existing security measures in the codebase:


Let's check for any existing security measures in the codebase, particularly around input validation, rate limiting, and SVG security. The previous search was too broad, so I've narrowed it down to specific patterns and file types.


The previous search found files handling SVG content. Let's examine their implementation to understand the security measures in place.


From the previous output, I can see that badge generation is handled by the badge-maker package and there's a validateBadgeStyle function being used. Let's check these validation mechanisms and the database layer to complete our security analysis.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for input validation and rate limiting
rg -i "sanitize|rate.?limit|xss|injection" 
# Check for security-related dependencies
rg -g "package*" "security|sanitize|rate.?limit"

Length of output: 36666


Script:

#!/bin/bash
# Check for input validation and sanitization
ast-grep --pattern 'validate($$$)' 
ast-grep --pattern 'sanitize($$$)'

# Check for rate limiting middleware or similar
rg -l "rate.?limit|throttle" --type ts

# Check for SVG validation
rg -l "svg|xss" --type ts

# Check badge-maker package security
rg -g "package*" "badge-maker"

Length of output: 358


Script:

#!/bin/bash
# Let's examine the SVG handling in the identified files
rg -A 10 -B 10 "svg|xss" utils/badges-admin.ts api/badges.ts

# Check for any input validation in badge generation
rg -A 10 -B 10 "makeBadge" utils/badges-admin.ts api/badges.ts

# Look for any security-related checks in badge data handling
rg -A 10 -B 10 "badgeData|redirectUrl" utils/badges-admin.ts api/badges.ts

Length of output: 24597


Script:

#!/bin/bash
# Check for input validation in utils
rg -A 5 "validate" lib/utils.ts

# Check badge-maker package version and any security features
rg -g "package*|deno.json*" "badge-maker"

# Check if there's any sanitization in the db layer
rg -A 10 "setBadgeData" lib/db.ts

Length of output: 939

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c8265d6 and c4f36f9.

⛔ Files ignored due to path filters (1)
  • deno.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • .env (1 hunks)
  • .env.prod (1 hunks)
  • .gitignore (1 hunks)
  • .tool-versions (1 hunks)
  • .vscode/extensions.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • api/admin.ts (1 hunks)
  • api/badges.ts (12 hunks)
  • api/meta.ts (1 hunks)
  • deno.json (1 hunks)
  • docs/README.md (1 hunks)
  • docs/api-terms.md (1 hunks)
  • docs/hackclub-badges.md (2 hunks)
  • docs/logos.md (1 hunks)
  • docs/self-hosting.md (1 hunks)
  • lib/cli/kv-seed.json (3 hunks)
  • lib/cli/logger.ts (2 hunks)
  • lib/config.ts (1 hunks)
  • lib/db.ts (2 hunks)
  • lib/githubAuth.ts (1 hunks)
  • lib/logos.ts (1 hunks)
  • lib/metadata.ts (2 hunks)
  • lib/utils.ts (1 hunks)
  • main.ts (2 hunks)
  • utils/badges-admin.ts (7 hunks)
  • utils/kv-keys-debugger.ts (1 hunks)
  • utils/manage-api-users.ts (2 hunks)
  • utils/preseed-kv.ts (1 hunks)
  • utils/types.ts (2 hunks)
🧰 Additional context used
🪛 Biome
api/badges.ts

[error] 53-53: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)

utils/manage-api-users.ts

[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🪛 LanguageTool
docs/api-terms.md

[uncategorized] ~10-~10: Possible missing comma found.
Context: ...point(s) for more than 5 requests per minute and respect the cache headers. - The AP...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~16-~16: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...ablize things at any point in the future and you also agree to any revisions we may ...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~23-~23: A comma may be missing after the conjunctive/linking adverb ‘Currently’.
Context: ...ooks.wiki/legal/tos ## Privacy Policy Currently we do not collect IP addresses from eac...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)

docs/hackclub-badges.md

[style] ~3-~3: ‘for the benefit’ might be wordy. Consider a shorter alternative.
Context: ... badges are maintained by @ajhalili2006 for the benefit of Hack Club Slack community, especiall...

(EN_WORDINESS_PREMIUM_FOR_THE_BENEFIT)


[uncategorized] ~3-~3: You might be missing the article “the” here.
Context: ...ned by @ajhalili2006 for the benefit of Hack Club Slack community, especially those ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~5-~5: The hyphen in fiscally-hosted is redundant.
Context: ...ng Arcade 2024 and open-source projects fiscally-hosted under HCB. ## Hack Clubber? If you're...

(ADVERB_LY_HYPHEN_FIX)


[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...hour)? Use this badge, alongside adding hackclub-arcade tag to your repository ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: Possible missing comma found.
Context: ...dge, alongside adding hackclub-arcade tag to your repository for discoverability....

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~68-~68: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 2470 characters long)
Context: ...hackclub.com/arcade) Other styles: Built during Arcade 2024 Built during Arcade 2024 [![Built during Arcade 2024](https://badge...

(EN_EXCESSIVE_EXCLAMATION)


[uncategorized] ~126-~126: The hyphen in fiscally-hosted is redundant.
Context: ...`` ## Donate on HCB For organizations fiscally-hosted through HCB, you can use the Donate on ...

(ADVERB_LY_HYPHEN_FIX)


[uncategorized] ~135-~135: The hyphen in fiscally-hosted is redundant.
Context: ...hq) ## HCB Balances For organizations fiscally-hosted through HCB, you can also show your bal...

(ADVERB_LY_HYPHEN_FIX)

docs/logos.md

[grammar] ~20-~20: The word ‘clipboard’ is not correct in this context. Use one of the suggestions or replace it with an appropriate verb.
Context: ...4 --wrap=0 and copy the encoded string to clipboard. 3. Open the [lib/cli/kv-seed.json`](....

(VB_TO_NN_DT)


[grammar] ~40-~40: The word ‘clipboard’ is not correct in this context. Use one of the suggestions or replace it with an appropriate verb.
Context: ...4 --wrap=0and copy the encoded string to clipboard. 3. Run dotenvx run -f .env.prod --...

(VB_TO_NN_DT)

docs/self-hosting.md

[uncategorized] ~14-~14: “you” seems less likely than “you’re” (you are).
Context: ...r PAT (via DENO_KV_ACCESS_TOKEN) if you doing presistence on the cloud. - `DENO...

(AI_HYDRA_LEO_CP_YOU_YOUARE)


[uncategorized] ~26-~26: Although a hyphen is possible, it is not necessary in a compound modifier in which the first word is an adverb that ends in ‘ly’.
Context: ...nd. 3. Pre-seed the KV backend from our manually-crafted production export: `deno task utils:...

(HYPHENATED_LY_ADVERB_ADJECTIVE)


[uncategorized] ~28-~28: You might be missing the article “an” here.
Context: ...admin APIs with your GitHub PAT, create api-admins team on your organization...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[uncategorized] ~28-~28: The preposition “in” seems more likely in this position than the preposition “on”.
Context: ...ur GitHub PAT, create api-admins team on your organization. (or reuse a exist...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_ON_IN)


[misspelling] ~29-~29: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...team on your organization. (or reuse a existing team) and use the team slug as...

(EN_A_VS_AN)

🔇 Additional comments (27)
.gitignore (1)

4-4: LGTM! Verify pattern effectiveness.

The wildcard pattern *.decrypted is a good security practice to prevent any decrypted sensitive files from being accidentally committed.

Let's verify that the pattern works as expected:

✅ Verification successful

Pattern *.decrypted works correctly for ignoring sensitive files

The verification confirms:

  • No .decrypted files are currently tracked in the repository
  • The pattern successfully ignores files with .decrypted extension in both root and subdirectories
  • Git correctly recognizes and applies the pattern from line 4 of .gitignore
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the .gitignore pattern correctly ignores .decrypted files
# and that no such files are currently tracked.

# Test 1: Check if any .decrypted files are currently tracked
echo "Checking for tracked .decrypted files..."
git ls-files "*.decrypted"

# Test 2: Verify the pattern is recognized by git
echo "Verifying .gitignore pattern..."
git check-ignore -v "test.decrypted"
git check-ignore -v "subfolder/secret.decrypted"

Length of output: 370

.vscode/extensions.json (1)

2-5: LGTM! The recommended extensions are appropriate for the project.

The extensions recommended are essential for the development workflow:

  • denoland.vscode-deno: Provides Deno language support and tooling
  • vivaxy.vscode-conventional-commits: Helps maintain consistent commit messages

Let's verify the project actually uses Deno:

✅ Verification successful

✅ The Deno extension recommendation is appropriate for this project

This is a Deno project as evidenced by:

  • Presence of deno.json with comprehensive Deno configuration
  • Deno-specific tasks for running, deploying, and managing the application
  • TypeScript files using .ts extension with Deno-style imports
  • Project uses Deno Deploy for deployment

The conventional commits extension is also appropriate for maintaining consistent commit messages in any project.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify Deno usage in the project
# Test: Look for Deno configuration files and imports
echo "Checking for Deno configuration..."
rg -l "deno" && fd -g "deno.{json,jsonc}"

Length of output: 208


Script:

#!/bin/bash
# Let's verify the actual content of deno.json and check imports
echo "=== Checking deno.json configuration ==="
cat deno.json

echo -e "\n=== Checking Deno imports in TypeScript files ==="
rg "^import.*from.*" lib/metadata.ts

Length of output: 2046

docs/README.md (2)

10-10: LGTM!

The license declaration is clear and appropriate.


3-6: Verify documentation links accessibility.

The links appear to be well-structured, but let's verify they are accessible and up-to-date.

✅ Verification successful

Documentation links are valid and well-maintained

All local documentation files exist and contain appropriate content:

  • api-terms.md contains API terms with recent updates
  • hackclub-badges.md describes the Hack Club badges functionality
  • logos.md covers badge logo information
  • Admin API documentation is present in self-hosting.md

The external API docs link (badges.api.lorebooks.wiki/docs) is the only link that can't be verified in the sandbox environment, but it follows a standard subdomain pattern for API documentation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if all referenced documentation files exist in the repository

echo "Checking local documentation files..."
for file in "api-terms.md" "hackclub-badges.md" "logos.md"; do
  if [ -f "docs/$file" ]; then
    echo "✓ docs/$file exists"
  else
    echo "⨯ docs/$file is missing"
  fi
done

Length of output: 555


Script:

#!/bin/bash
# Let's verify the content of these files to ensure they are properly formatted markdown files
# and contain relevant content

echo "=== Content of api-terms.md ==="
head -n 5 docs/api-terms.md

echo -e "\n=== Content of hackclub-badges.md ==="
head -n 5 docs/hackclub-badges.md

echo -e "\n=== Content of logos.md ==="
head -n 5 docs/logos.md

# Also check if there's any admin API documentation
echo -e "\nSearching for admin API documentation..."
rg -i "admin.*api|api.*admin" docs/ -A 2

Length of output: 1802

.vscode/settings.json (2)

13-14: LGTM: Conventional commits scope additions are appropriate.

The addition of "global" and "vscode" scopes to conventional commits configuration makes sense as they represent common areas of change in the codebase.


22-22: Verify impact of disabling dotenv autocloaking.

Disabling dotenv.enableAutocloaking means environment variables will be visible in the editor. While this can be helpful during development, ensure this doesn't lead to accidental commits of sensitive information.

Let's check if there are any unprotected environment files:

✅ Verification successful

Let me check the content of these .env files and .gitignore to better understand the situation.


Environment files are properly secured with encryption

The environment files (.env and .env.prod) are tracked in the repository but they contain only encrypted values using dotenv's public-key encryption system. All sensitive values are encrypted with the prefix "encrypted:" and the files include the corresponding public keys. This is a secure practice as the private keys needed for decryption are not stored in the repository.

  • .gitignore correctly excludes .env.keys which likely contains the private keys
  • Both .env and .env.prod use proper encryption for all sensitive values
  • The autocloaking setting is safe in this context as it only affects how VSCode displays already-encrypted values
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for .env files that aren't properly gitignored
# Expected: All .env files should be in .gitignore

# Test 1: Check if .env files are properly gitignored
rg -l "\.env" .gitignore

# Test 2: List any tracked .env files that might expose secrets
git ls-files | rg "\.env"

Length of output: 80


Script:

#!/bin/bash
# Check the content of .gitignore for .env patterns
echo "=== .gitignore patterns ==="
rg "\.env" .gitignore

# Check if .env files contain actual secrets or are just templates
echo -e "\n=== .env file content ==="
cat .env || echo "Cannot read .env"
echo -e "\n=== .env.prod file content ==="
cat .env.prod || echo "Cannot read .env.prod"

Length of output: 2467

lib/utils.ts (1)

1-16: Verify the impact of style validation.

The function is used in badge generation, so let's verify its integration.

✅ Verification successful

Let me analyze the usage of validateBadgeStyle further by checking the implementation in api/badges.ts to understand the complete flow.


Style validation implementation is correctly integrated and safe

The validation function is properly integrated across the codebase:

  • Used consistently in all badge generation endpoints (hcbBalanceOps, hcbDonateButton, generateSvg)
  • Matches the type definition in lib/db.ts which defines the valid style values
  • Provides a safe fallback to "flat" style when validation fails
  • Correctly handles both direct style parameter and style from stored badge data
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of validateBadgeStyle and potential edge cases

# Test 1: Find all imports of validateBadgeStyle
echo "=== Checking imports ==="
rg "import.*validateBadgeStyle" -A 2

# Test 2: Find all direct usage of the function
echo -e "\n=== Checking function usage ==="
rg "validateBadgeStyle\(" -A 2

# Test 3: Check if there are any hardcoded badge styles that might need validation
echo -e "\n=== Checking for hardcoded styles ==="
rg -i "style: ['\"](?:plastic|flat|flat-square|for-the-badge|social)['\"]" -A 2

Length of output: 1468


Script:

#!/bin/bash
# Check the implementation in api/badges.ts to understand how the style parameter is handled
echo "=== Checking badges.ts implementation ==="
cat api/badges.ts

# Also check if there are any tests for validateBadgeStyle
echo -e "\n=== Checking for tests ==="
fd "test" --exec grep -l "validateBadgeStyle"

Length of output: 10734

utils/kv-keys-debugger.ts (1)

12-14: LGTM! The console log formatting looks cleaner.

The template string formatting change improves readability while maintaining the same output information.

api/meta.ts (2)

6-8: LGTM! Good use of TypeScript features and OpenAPI specifications.

The override keyword and tags implementation follow best practices. The endpoint is properly categorized under "meta" which improves API documentation organization.


Line range hint 23-28: LGTM! Clean and consistent implementation.

The handle method is properly overridden and its implementation matches the defined schema. The response structure is clear and appropriate for a health check endpoint.

.env.prod (1)

8-9: LGTM! Proper security measures in place.

The environment variables are properly encrypted using dotenvx.com's encryption mechanism, which is a secure approach for storing sensitive credentials in version control.

api/admin.ts (1)

20-40: Consider adding rate limiting for the authentication endpoint.

To prevent potential brute-force attacks or API abuse, consider implementing rate limiting for this authentication endpoint.

Let's check if rate limiting is implemented elsewhere:

lib/metadata.ts (2)

39-39: LGTM!

The updated description provides better clarity about the development environment.


43-43: LGTM!

The added newline improves readability.

deno.json (1)

8-8: LGTM! Good practice to specify version constraints.

The addition of version constraint ^21.0.2 for @octokit/rest is a good practice as it:

  • Locks the major version to prevent breaking changes
  • Allows minor and patch updates for bug fixes and compatible features

Let's verify version compatibility with other dependencies:

✅ Verification successful

Version constraint for @octokit/rest is appropriate and safely used

The codebase uses @octokit/rest in a standard way for GitHub API authentication and team management. The version constraint ^21.0.2 is appropriate as:

  • It's used for basic Octokit instantiation with auth tokens
  • No deprecated or version-specific APIs are being used
  • The implementation follows current Octokit patterns
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any version conflicts or related dependencies
# Look for other @octokit related imports or version specifications
rg -i "octokit" --type json

Length of output: 87


Script:

#!/bin/bash
# Let's check for actual usage of @octokit/rest in the codebase
rg -l "@octokit/rest" 

# Also check for any TypeScript/JavaScript files that might use Octokit
rg -l "Octokit" 

Length of output: 145


Script:

#!/bin/bash
# Let's examine how @octokit/rest is used in these files to ensure compatibility
rg -A 5 -B 5 "@octokit/rest" lib/githubAuth.ts utils/manage-api-users.ts

Length of output: 990

lib/db.ts (1)

38-38: LGTM: Style consistency improvement

The addition of a trailing comma aligns with modern TypeScript style conventions and makes future parameter additions cleaner in version control.

docs/logos.md (1)

46-48: LGTM!

The footnote provides clear and helpful information about the CDN URLs.

docs/hackclub-badges.md (2)

11-17: LGTM! Clear instructions for Hack Club HQ members.

The section provides clear guidance with proper links to utilities and communication channels.


21-57: LGTM! Comprehensive badge usage documentation.

The section provides clear examples with:

  • Visual demonstrations of all badge styles
  • Well-formatted markdown code blocks
  • Proper linking to the Hack Club website
lib/cli/kv-seed.json (3)

3-7: LGTM: Icon updates improve theme support

The changes appropriately split the HCB icon into light and dark variants while adding a new arcade-specific icon. This improves theme support and visual consistency.


33-33: LGTM: Badge logo update matches new icon naming

The badge logo update from "hackclub" to "hackclub-light" maintains consistency with the new light/dark icon variants.


46-49: LGTM: Badge configuration updates improve UX

The changes enhance the HCB badge by:

  1. Using the new dark variant of the HCB icon
  2. Updating links to more specific and relevant URLs
main.ts (4)

1-11: LGTM!

The new imports are relevant and necessary for the added functionality in this file.


14-26: Verify the CORS configuration.

The CORS configuration looks good. It allows all origins, exposes the necessary headers, and supports the required HTTP methods. However, please ensure that the credentials option is intentionally set to true. Setting it to true means that the browser will include cookies in cross-origin requests, which may have security implications if not properly handled.


80-99: Ensure proper error handling and logging for the GitHub authentication process.

The implementation of the GitHub authentication using the bearerAuth middleware and the handleGitHubAuth function looks good. However, it's important to ensure proper error handling and logging throughout the authentication process. Consider the following:

  1. In the verifyToken function, add error handling for the case when handleGitHubAuth throws an error or returns false. You may want to log the error and return an appropriate error response to the client.

  2. Inside the handleGitHubAuth function (assuming it's defined in lib/githubAuth.ts), ensure that any errors that occur during the authentication process are properly caught, logged, and handled. This includes errors that may occur when making API requests to GitHub or processing the response.

  3. Consider adding logging statements at key points in the authentication flow to help with debugging and monitoring. For example, you can log when a token is successfully verified or when an authentication error occurs.

If you need assistance with implementing the error handling and logging, I can provide more detailed suggestions or code examples. Just let me know!


105-109: LGTM!

The new route for /hcb/balances correctly redirects to the /hcb/balance endpoint while preserving any query parameters. This is a good way to handle alternative route names and maintain compatibility.

api/badges.ts (1)

207-210: ⚠️ Potential issue

Define color and links in the request query schema

You're destructuring color and links from apiReqData.query, but these parameters are not defined in your query schema. As a result, they will always be undefined.

Add color and links to your request query schema to ensure they are properly validated and available:

request: {
  query: z.object({
    json: Bool({
      required: false,
      description: "Whether to force pull JSON data from KV or not",
    }),
    style: Str({
      description: "Badge style as supported by `badge-maker` npm library.",
      required: false,
      default: "flat",
    }),
+   color: Str({
+     description: "Color of the badge.",
+     required: false,
+   }).optional(),
+   links: z.array(Str()).optional(),
  }),
},

Likely invalid or redundant comment.

Comment thread .tool-versions
Comment thread lib/utils.ts Outdated
Comment thread lib/config.ts
Comment thread lib/config.ts Outdated
Comment thread lib/config.ts
Comment thread lib/githubAuth.ts Outdated
Comment thread lib/githubAuth.ts Outdated
Comment thread lib/githubAuth.ts Outdated
Comment thread api/badges.ts
Comment thread api/badges.ts Outdated
…rabbitai

Including some QoL improvements to parts of the codebase.

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Also finalize self-hosting docs ftw

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
…for Deno.KV.get

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
…ions

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
@deno-deploy deno-deploy Bot had a problem deploying to Preview November 9, 2024 18:53 Failure
Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <ajhalili2006@crew.recaptime.dev>
@ajhalili2006
Copy link
Copy Markdown
Member Author

I might ship this with just the barebones for now and implement the rest of the API later.

@ajhalili2006
Copy link
Copy Markdown
Member Author

I probably forgot this lol, but will work on it being merged soon :)

@ajhalili2006 ajhalili2006 marked this pull request as ready for review March 13, 2026 19:49
Copilot AI review requested due to automatic review settings March 13, 2026 19:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a GitHub PAT–protected Admin API to the Deno/Hono backend, adds edge caching for selected routes, and expands/updates project documentation and scripts to support self-hosting and administration.

Changes:

  • Adds /admin/* endpoints with bearer-token authentication backed by GitHub org/team membership, plus KV-based caching of auth checks.
  • Adds Cache API edge-caching for /hcb/* and /badges/* routes and extends HCB badge functionality (e.g., total raised).
  • Updates docs, tooling, and repo metadata (Deno lock/imports, scripts, contributor docs, VS Code settings).

Reviewed changes

Copilot reviewed 31 out of 34 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
utils/types.ts Adds a DB-store type wrapper for KV badge payloads.
utils/preseed-kv.ts Improves KV seeding flow and error handling; replaces async forEach with for..of.
utils/manage-api-users.ts Refactors admin-user management CLI and improves error messaging.
utils/kv-keys-debugger.ts Minor formatting/log output adjustment.
utils/badges-admin.ts Tightens icon upload validation and modifies badge/redirect creation logic.
main.ts Adds edge cache middleware and introduces /admin/* auth + write-auth for /badges/*.
lib/utils.ts Adds badge-style validation helper.
lib/metadata.ts Updates OpenAPI tags/metadata and description text.
lib/logos.ts Replaces prior logo logic with simple-icons resolution + KV caching.
lib/hcb.ts Updates module docstrings.
lib/githubAuth.ts Adds GitHub PAT auth handling with KV caching and token hashing.
lib/db.ts Adds typed KV reads and updates icon resolution behavior.
lib/config.ts Adds config/docs and GitHub token validation helper; adds cache-related flags.
lib/cli/logger.ts Formatting cleanup and object-message handling tweak.
docs/self-hosting.md Expands env var docs and deployment instructions.
docs/logos.md Updates logo docs and KV seeding instructions.
docs/hackclub-badges.md Adds usage examples and expands HCB badge documentation.
docs/deno-scripts.md New doc page describing common Deno scripts/tasks.
docs/api-terms.md Updates ToS text, privacy notes, and adds Hack Club badges addendum.
docs/README.md Restructures docs index and adds contributor/self-hosting pointers.
deno.lock Updates lockfile format and dependency versions.
deno.json Pins Octokit version; minor deploy config tweak.
api/meta.ts Adds OpenAPI tags and uses override for schema/handle.
api/badges.ts Adds HCB “total raised”, caching headers, style validation, and adjusts JSON/SVG response logic.
api/admin.ts New Admin API routes for auth testing and granting admin access.
README.md Updates project branding, adds badges, and clarifies local run instructions.
CONTRIBUTING.md Adds contributor onboarding and patch-submission guidelines.
.vscode/settings.json Adds editor/conventional-commit related settings.
.vscode/extensions.json Formats extension recommendations.
.tool-versions Pins Deno version for tool managers.
.gitignore Broadens decrypted-file ignore pattern.
.env.prod Adds encrypted GITHUB_TOKEN for prod env.
.env Adds encrypted GITHUB_TOKEN for dev env.
Comments suppressed due to low confidence (2)

api/badges.ts:288

  • apiReqData.query schema only defines json and style, but the handler destructures color and links. This will fail TypeScript type-checking and also indicates these query params are either missing from the schema or should be removed.
    const reqUrl = new URL(c.req.url);
    const { origin } = reqUrl;
    const { color, links, style } = apiReqData.query;

utils/badges-admin.ts:127

  • For --type redirect, data is typed as BadgeData but only redirectUrl is provided. For --type badge, required fields like redirectUrl are omitted. This will not type-check and reflects the underlying BadgeData type issue—split redirect vs badge payload types (or make the type a discriminated union) and build the correct shape for each branch.
    if (options.type == "redirect") {
      if (options.url == undefined) {
        logger.error(`you forgot to set --url option`);
      }
      const data: BadgeData = {
        redirectUrl: options.url,
      };
      const result = await db.setBadgeData(project, name, "redirect", data);

      if (result.ok == true) {
        logger.success(
          `added redirect on https://badges.api.lorebooks.wiki/badges/${project}/${name}`,
        );
      }
    }

    if (options.type == "badge") {
      const data: BadgeData = {
        label: options.label || null,
        labelColor: options.labelColor,
        color: options.color,
        message: options.message,
        logo: options.logo || null,
        style: options.style,
        links: options.links || null,
      };


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/db.ts
Comment on lines 95 to 100
export async function resolveBadgeIcon(icon: string) {
const kvApi = await kv(config.kvUrl);
let result: string | null
if (icon == null) {
return null;
return undefined;
}
Comment thread main.ts
@@ -1,19 +1,50 @@
import { Hono } from "hono";
import { Context, Hono, Next } from "hono";
Comment thread lib/githubAuth.ts
const msgBuffer = (msg: string) => new TextEncoder().encode(msg);
export const github = (token: string) => new Octokit({
auth: token,
userAgent: `@lorebooks-wiki/badges-api $({config.homepage})`
Comment thread lib/logos.ts
Comment on lines +32 to +33
const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
Comment thread api/badges.ts
message: data.message,
color: data?.color || "gray",
color: color || data?.color || "gray",
style: validateBadgeStyle(style || data?.color),
Comment thread lib/githubAuth.ts
}

/**
* Hash a GitHub token into SHA-215 encoded text.
Comment thread lib/logos.ts
Comment on lines +43 to 52
if (!iconName.startsWith("si:")) {
throw new Error("not a simple-icon icon name")
}
}

return svg2base64(iconSvg);
}

function prepareNamedLogo({ name, color, style, size }) {
if (typeof name !== "string") {
return undefined;
}
// check on our KV cache first
const cacheKv = await kvApi.get<string>(["badgeIcons", iconName])

name = name.toLowerCase();
// if not cached OR the cache busting is enabled, run the resolver
if (cacheKv.value === null || cacheBuster === true) {
const upstreamData = await resolveSimpleIcon(iconName)

Comment thread api/badges.ts
Comment on lines 205 to +207
const { org, style } = apiReqData.query;
const dbData = (await getBadgeData("hcb", "donate")).result?.data;
const badgeData: Format = {
label: dbData?.label,
labelColor: dbData?.labelColor,
logoBase64: await resolveBadgeIcon("hcb-dark"),
message: `Donate to ${org || "HQ"}`,
links: [
`https://hcb.hackclub.com/donations/start/${org || "hq"}`,
`https://hcb.hackclub.com/donations/start/${org || "hq"}`,
],
};
const badge = makeBadge(badgeData);
return c.newResponse(badge, 200, {
"Content-Type": "image/svg+xml",
});
const { result, code } = await getOrgData(org || "hq");
Comment thread lib/config.ts
kvUrl: Deno.env.get("DENO_KV_URL"),
github: {
authServiceToken: Deno.env.get("GITHUB_TOKEN"),
authServiceToken: getValidatedGithubToken(),
Comment thread api/badges.ts
Comment on lines +88 to +89
export class hcbTotalRasied extends OpenAPIRoute {
override schema = {
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
utils/badges-admin.ts (1)

101-115: ⚠️ Potential issue | 🔴 Critical

Abort redirect flow on missing --url and handle failed writes.

Right now, missing --url only logs an error and still proceeds. Also, redirect write failures are not surfaced (result.ok == false path is missing).

🛠️ Suggested patch
     if (options.type == "redirect") {
       if (options.url == undefined) {
         logger.error(`you forgot to set --url option`);
+        Deno.exit(1);
       }
       const data: BadgeData = {
         redirectUrl: options.url,
       };
       const result = await db.setBadgeData(project, name, "redirect", data);
-
-      if (result.ok == true) {
-        logger.success(
-          `added redirect on https://badges.api.lorebooks.wiki/badges/${project}/${name}`,
-        );
-      }
+      if (!result.ok) {
+        logger.error("Something went wrong while adding a redirect badge.");
+        logger.warn(result.error);
+        Deno.exit(1);
+      }
+      logger.success(
+        `added redirect on https://badges.api.lorebooks.wiki/badges/${project}/${name}`,
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/badges-admin.ts` around lines 101 - 115, When options.type ===
"redirect" abort early if options.url is undefined by logging the error and
returning/throwing before building BadgeData or calling db.setBadgeData; ensure
you reference options.url, options.type and the BadgeData shape. After calling
db.setBadgeData(project, name, "redirect", data) check result.ok and on false
log an error (including result.error or result message) and exit non-zero (or
throw) instead of silently continuing; on true keep the existing logger.success
path. This touches the redirect branch in utils/badges-admin.ts, specifically
the code around db.setBadgeData, BadgeData, logger.error, and logger.success.
🟡 Minor comments (17)
lib/metadata.ts-11-11 (1)

11-11: ⚠️ Potential issue | 🟡 Minor

Correct public-facing metadata typos.

Two strings appear in API docs output: communties and local host.

✏️ Suggested patch
-      "HCB badges for use by organizations and communties fiscally sponsored by Hack Club through the HCB platform.",
+      "HCB badges for use by organizations and communities fiscally sponsored by Hack Club through the HCB platform.",
...
-    description: "Development instance (local host)",
+    description: "Development instance (localhost)",

Also applies to: 39-39

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/metadata.ts` at line 11, Fix the typos in the public-facing metadata
strings in lib/metadata.ts: change "communties" to "communities" in the string
"HCB badges for use by organizations and communties fiscally sponsored by Hack
Club through the HCB platform." and change any "local host" occurrences to
"localhost" (also present elsewhere around the same file, e.g., the second
occurrence near line 39). Update these literal strings so the exported metadata
values (the description entries) are corrected.
docs/README.md-3-4 (1)

3-4: ⚠️ Potential issue | 🟡 Minor

Fix typo in the intro sentence.

exisitng should be existing.

✏️ Suggested patch
-Welcome to the Markdown documentation for our Badges API! We welcome improvements to this documentation if
-you found any issues with it or even expand exisitng content.
+Welcome to the Markdown documentation for our Badges API! We welcome improvements to this documentation if
+you found any issues with it or even expanded existing content.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/README.md` around lines 3 - 4, Fix the typo in the README intro
sentence: change the word "exisitng" to "existing" in the first paragraph
("Welcome to the Markdown documentation for our Badges API! ... expand exisitng
content.") so the sentence reads "... expand existing content.".
README.md-32-36 (1)

32-36: ⚠️ Potential issue | 🟡 Minor

Fix minor wording issues in the local/self-hosting section.

There are two text issues: overridenoverridden, and a more detailed instructionsmore detailed instructions.

✏️ Suggested patch
-It will listen on `localhost:8080` by default unless overriden by `PORT`
+It will listen on `localhost:8080` by default unless overridden by `PORT`
 environment variable.
 
-[See the docs](./docs/self-hosting.md) for a more detailed instructions on self-hosting
+[See the docs](./docs/self-hosting.md) for more detailed instructions on self-hosting
 and more.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 32 - 36, Update the README local/self-hosting text to
fix two typos: change "overriden" to "overridden" in the sentence mentioning
PORT and change "a more detailed instructions" to "more detailed instructions"
in the "See the docs" line (the line referencing "localhost:8080", "PORT", and
"./docs/self-hosting.md"). Ensure the corrected phrases appear exactly where
those original tokens are used.
docs/self-hosting.md-16-17 (1)

16-17: ⚠️ Potential issue | 🟡 Minor

Fix typo in DENO_KV_ACCESS_TOKEN description.

authenicateauthenticate.

✏️ Suggested patch
-- `DENO_KV_ACCESS_TOKEN` - Used to authenicate against a KV Connect-compatible
+- `DENO_KV_ACCESS_TOKEN` - Used to authenticate against a KV Connect-compatible
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/self-hosting.md` around lines 16 - 17, Fix the typo in the description
for the environment variable DENO_KV_ACCESS_TOKEN by changing "authenicate" to
"authenticate" in the docs text where the variable is described; ensure the
surrounding sentence remains grammatically correct and update any other
occurrences of the misspelling in the same file or nearby lines if present.
api/badges.ts-127-161 (1)

127-161: ⚠️ Potential issue | 🟡 Minor

Same missing return issue in hcbTotalRaised.handle

Similar to hcbBalanceOps, unhandled status codes will return undefined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/badges.ts` around lines 127 - 161, The handle method in hcbTotalRaised
(override async handle(c: Context)) only returns responses for code == 200 and
code == 404, leaving other getOrgData(result).code paths to return undefined;
add a final fallback branch that handles all other status codes (e.g., else or
default) and returns a proper SVG badge response or an error response so the
function always returns a Response. Use the same pattern as the existing
branches: build a badge via makeBadge (with resolveBadgeIcon("hcb-dark") and
validateBadgeStyle(style) if needed), and return c.newResponse(badgeSvg,
<appropriateStatus>, { "Content-Type": "image/svg+xml", "Cache-Control":
"max-age=300" }) to ensure every execution path returns a response.
docs/api-terms.md-34-34 (1)

34-34: ⚠️ Potential issue | 🟡 Minor

Spelling error: "Addeumn" should be "Addendum"

-## Addeumn: Unofficial Hack Club badges
+## Addendum: Unofficial Hack Club badges
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api-terms.md` at line 34, Fix the spelling in the markdown header:
change the header text "Addeumn: Unofficial Hack Club badges" to "Addendum:
Unofficial Hack Club badges" so the section title uses the correct word
("Addendum").
lib/githubAuth.ts-156-159 (1)

156-159: ⚠️ Potential issue | 🟡 Minor

Typo in documentation: "SHA-215" should be "SHA-512"

The JSDoc comment incorrectly states "SHA-215" when the implementation uses "SHA-512".

 /**
- * Hash a GitHub token into SHA-215 encoded text.
+ * Hash a GitHub token into SHA-512 encoded hex.
  * `@param` token Any string, but for this case, a GitHub token
- * `@returns` 
+ * `@returns` A hex-encoded SHA-512 hash of the token
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/githubAuth.ts` around lines 156 - 159, Update the JSDoc for the token
hashing function in lib/githubAuth.ts to fix the typo: replace "SHA-215" with
"SHA-512" so the documentation matches the implementation (the JSDoc block
starting "Hash a GitHub token into SHA-215 encoded text." should read SHA-512).
docs/api-terms.md-38-39 (1)

38-39: ⚠️ Potential issue | 🟡 Minor

Spelling error: "affliated" should be "affiliated"

-you agree to only use them in connection with your personal projects or hackathons and events affliated
+you agree to only use them in connection with your personal projects or hackathons and events affiliated
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api-terms.md` around lines 38 - 39, Replace the misspelled word
"affliated" with the correct spelling "affiliated" in the terms text (look for
the sentence containing "you agree to only use them in connection with your
personal projects or hackathons and events affliated with Hack Club HQ.");
update that token so the sentence reads "...events affiliated with Hack Club
HQ."
docs/api-terms.md-15-17 (1)

15-17: ⚠️ Potential issue | 🟡 Minor

Spelling error: "stablize" should be "stabilize"

-We may change the API terms as we implement more features and stablize things at
+We may change the API terms as we implement more features and stabilize things at
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api-terms.md` around lines 15 - 17, Fix the spelling typo in the API
terms paragraph: replace the incorrect word "stablize" with "stabilize" in the
sentence "We may change the API terms as we implement more features and stablize
things..." in docs/api-terms.md; also scan the file for any other occurrences of
"stablize" and correct them to "stabilize".
api/admin.ts-133-143 (1)

133-143: ⚠️ Potential issue | 🟡 Minor

Unsafe access to error properties without type narrowing

Accessing error.response.data and error.status without checking if error has these properties could throw a TypeError if the error is not an Octokit error (e.g., network failure).

🛡️ Proposed fix with type narrowing
     } catch (error) {
+      const ghError = error as { response?: { data?: unknown }; status?: number };
       return c.json({
         ok: false,
         error: {
           code: "GITHUB_API_ERROR",
           message: "Something went wrong while adding into the API admins team.",
-          ghApiResult: error.response.data,
-          ghApiStatusCode: error.status
+          ghApiResult: ghError.response?.data ?? null,
+          ghApiStatusCode: ghError.status ?? 500
         }
-      })
+      }, ghError.status ?? 500)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/admin.ts` around lines 133 - 143, The catch block in the API admin
handler is unsafely accessing error.response.data and error.status; update the
catch (error) handling in api/admin.ts (the block that returns the
GITHUB_API_ERROR payload) to narrow and safely extract those fields—check if
error is an object and has response and response.data (or use optional chaining
like error?.response?.data) and a numeric status (or fallback to null/unknown),
then populate ghApiResult and ghApiStatusCode with those safe values (and
optionally include the raw error in a debug log) instead of directly accessing
properties that may not exist.
utils/manage-api-users.ts-104-105 (1)

104-105: ⚠️ Potential issue | 🟡 Minor

Incorrect type comparison: status code is a number, not a string

err.status is a number, but comparing to "404" (string). This comparison will fail due to type mismatch even with loose equality in some cases.

-    if (err.status == "404") {
+    if (err.status === 404) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/manage-api-users.ts` around lines 104 - 105, The comparison in
utils/manage-api-users.ts is using a string literal for HTTP status; change the
check that currently inspects err.status == "404" to compare against a number
(e.g. err.status === 404) or coerce the value to a number before comparing so
the condition correctly detects a 404; update the branch that calls
logger.warn(`user ${username} is not in organization`) to use the numeric
comparison (or Number(err.status) === 404) so the warning fires as intended.
lib/logos.ts-59-61 (1)

59-61: ⚠️ Potential issue | 🟡 Minor

Incorrect Error construction loses error details

throw new Error(error) coerces the error object to a string [object Object], losing the original error message and stack trace.

🐛 Proposed fix
   } catch (error) {
-    throw new Error(error)
+    throw error;
   }

Or to add context:

   } catch (error) {
-    throw new Error(error)
+    throw new Error(`Failed to lookup simple icon: ${iconName}`, { cause: error });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/logos.ts` around lines 59 - 61, The catch block currently does "throw new
Error(error)" which coerces the error object and loses message/stack; update the
catch in lib/logos.ts to rethrow the original error (e.g., "throw error") or, if
you need to add context, wrap it using Error's cause option (e.g., "throw new
Error('failed to ...', { cause: error })") so the original error details are
preserved for debugging.
main.ts-37-41 (1)

37-41: ⚠️ Potential issue | 🟡 Minor

Loose equality comparison with potentially mismatched types

config.flags.edgeCache == true uses loose equality. If edgeCache is the string "true" (common for env vars), this comparison will fail. Use strict equality with proper type coercion.

🐛 Proposed fix
-if (Deno.env.get("DENO_DEPLOYMENT_ID") && Deno.env.get("DENO_REGION") || config.flags.edgeCache == true) {
+if ((Deno.env.get("DENO_DEPLOYMENT_ID") && Deno.env.get("DENO_REGION")) || config.flags.edgeCache === true || config.flags.edgeCache === "true") {

Or better, normalize the flag type in lib/config.ts to always be boolean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.ts` around lines 37 - 41, The condition uses loose equality on
config.flags.edgeCache which can be a string; update the check to use strict
boolean logic or normalize the flag to a boolean in your config loader: either
change the condition to use Boolean(config.flags.edgeCache) (or
config.flags.edgeCache === true) when deciding to enable edge cache for
app.get("/hcb/*", edgeCache(...)) and app.get("/badges/*", edgeCache(...)), or
better convert config.flags.edgeCache to a real boolean in lib/config.ts so
callers (like the if in main.ts) can simply test the boolean value.
api/badges.ts-51-85 (1)

51-85: ⚠️ Potential issue | 🟡 Minor

Missing return statement for unhandled status codes

The handle method only returns a response for code == 200 and code == 404. Other status codes (e.g., 500, 403) will result in undefined being returned, which may cause unexpected behavior.

🛡️ Proposed fix to handle other status codes
     } else if (code == 404) {
       const badgeSvg = makeBadge({
         label: `HCB balance for unknown organization`,
         labelColor: "EC3750",
         message: `USD 0`,
         logoBase64: await resolveBadgeIcon("hcb-dark"),
         style: validateBadgeStyle(style)
       });
       return c.newResponse(badgeSvg, 404, {
         "Content-Type": "image/svg+xml",
         "Cache-Control": "max-age=300",
       });
+    } else {
+      const badgeSvg = makeBadge({
+        label: "error",
+        message: "something went wrong",
+        color: "red",
+        style: validateBadgeStyle(style)
+      });
+      return c.newResponse(badgeSvg, code, {
+        "Content-Type": "image/svg+xml",
+      });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/badges.ts` around lines 51 - 85, The handle method in the class fails to
return for status codes other than 200 and 404; update the method (inside handle
and where getOrgData is called) to handle all other responses by returning a
sensible response: create a default badge or JSON error response that includes
the numeric code and a brief message, use the same headers ("Content-Type":
"image/svg+xml", "Cache-Control": "max-age=300") if you return an SVG badge (or
appropriate headers for JSON), and ensure every code path ends with return
c.newResponse(..., code, headers). Locate the logic around getOrgData(...) and
the existing if (code == 200) / else if (code == 404) branches and add a final
else that constructs and returns the fallback response using the incoming style
and resolveBadgeIcon("hcb-dark")/validateBadgeStyle as appropriate.
api/badges.ts-88-88 (1)

88-88: ⚠️ Potential issue | 🟡 Minor

Typo in class name: hcbTotalRasied should be hcbTotalRaised

The class name contains a spelling error that will propagate to the OpenAPI schema and any code referencing this class.

📝 Proposed fix
-export class hcbTotalRasied extends OpenAPIRoute {
+export class hcbTotalRaised extends OpenAPIRoute {

Also update the import in main.ts:

-import { generateSvg, hcbBalanceOps, hcbDonateButton, hcbTotalRasied } from "./api/badges.ts";
+import { generateSvg, hcbBalanceOps, hcbDonateButton, hcbTotalRaised } from "./api/badges.ts";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/badges.ts` at line 88, Rename the misspelled class hcbTotalRasied to
hcbTotalRaised and update all references/imports accordingly (e.g., the import
in main.ts) so the OpenAPI schema and any consumers use the corrected class
name; ensure the exported name and any places that instantiate or import
hcbTotalRasied are updated to hcbTotalRaised.
lib/db.ts-107-109 (1)

107-109: ⚠️ Potential issue | 🟡 Minor

Incorrect Error construction and loss of stack trace

throw Error(error) should use new Error(). Additionally, wrapping the error loses the original stack trace. Consider re-throwing directly or using Error.cause.

🐛 Proposed fix
   } catch (error) {
-    throw Error(error);
+    throw error;
   }

Or if you want to add context:

   } catch (error) {
-    throw Error(error);
+    throw new Error(`Failed to resolve badge icon: ${icon}`, { cause: error });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/db.ts` around lines 107 - 109, In lib/db.ts replace the incorrect "throw
Error(error)" in the catch block with either re-throwing the original error
(throw error) to preserve the original stack, or if you need added context
create a new Error using "new Error(message, { cause: error })" so the original
error is preserved as the cause and the stack is retained; locate the catch
block where Error(error) is called and apply one of these fixes.
utils/manage-api-users.ts-154-155 (1)

154-155: ⚠️ Potential issue | 🟡 Minor

Same incorrect type comparison for status code

-      if (err.status == "404") {
+      if (err.status === 404) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/manage-api-users.ts` around lines 154 - 155, The code is comparing
err.status to the string "404" which is the wrong type; update the error check
in the function handling API user membership (the block that currently reads if
(err.status == "404") { logger.warn(`membership status: not in team or org`); })
to compare against a numeric status (e.g. err.status === 404) or normalize the
value first (e.g. parseInt(err.status) === 404) and keep the existing
logger.warn call; ensure you also handle other truthy/undefined cases safely by
checking for err.status or err.statusCode consistently.
🧹 Nitpick comments (8)
utils/types.ts (1)

1-1: Use a type-only import for BadgeData to reduce runtime coupling.

BadgeData is used only as a type here; switching to import type avoids keeping an unnecessary value-level dependency on lib/db.ts.

-import { BadgeData } from "../lib/db.ts";
+import type { BadgeData } from "../lib/db.ts";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/types.ts` at line 1, Replace the value-level import of BadgeData with a
type-only import to avoid runtime coupling: change the import of BadgeData in
utils/types.ts to a type import (use the symbol BadgeData and the module
../lib/db.ts) so the compiler treats it only as a type and does not retain a
runtime dependency on lib/db.ts.
api/admin.ts (2)

40-40: Use strict equality operator

-    if (parsedAuthHeader[1] == "null") {
+    if (parsedAuthHeader[1] === "null") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/admin.ts` at line 40, The conditional in the admin auth check uses loose
equality ("if (parsedAuthHeader[1] == \"null\")")—change it to strict equality
using === to avoid type-coercion bugs; update the condition in the same
auth-handling block (where parsedAuthHeader is inspected) to use
"parsedAuthHeader[1] === 'null'".

147-157: Incomplete OpenAPIRoute implementations

The revokeAdminAccess, getAdminUserInfo, and listAdmins classes are empty stubs. Consider adding schema and handle methods or marking them as not-implemented to avoid runtime errors if these routes are registered.

Would you like me to generate skeleton implementations for these endpoints, or create GitHub issues to track this work?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/admin.ts` around lines 147 - 157, The three OpenAPIRoute classes
revokeAdminAccess, getAdminUserInfo, and listAdmins are empty stubs and must
either implement route metadata and handlers or be explicitly disabled;
implement for each a static schema() method (HTTP method, path params/body/query
schemas, responses) and an async handle(req, res) that validates input, calls
the appropriate service (e.g., AdminService.revoke, AdminService.getInfo,
AdminService.list), and returns proper HTTP status codes and error handling, or
if you prefer to block registration throw/return a clear "NotImplemented" error
in handle to prevent runtime routing errors; reference the class names
revokeAdminAccess, getAdminUserInfo, and listAdmins when making the changes.
lib/db.ts (2)

97-97: Missing semicolon in variable declaration

-  let result: string | null
+  let result: string | null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/db.ts` at line 97, The variable declaration "let result: string | null"
is missing a trailing semicolon; update the declaration of the variable named
result (the line containing "let result: string | null") to include a semicolon
at the end so the TypeScript syntax is correct.

98-99: Use strict equality when comparing with null

Use === instead of == to avoid unexpected type coercion.

-  if (icon == null) {
+  if (icon === null) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/db.ts` around lines 98 - 99, The comparison uses loose equality (icon ==
null) which allows type coercion; replace it with a strict check—either use icon
=== null if you only want to detect null, or explicitly check both absence cases
with icon === null || icon === undefined to preserve the current semantics;
update the conditional around the icon variable (the if handling the icon value)
accordingly so behavior remains correct and uses strict equality.
lib/githubAuth.ts (2)

49-49: Use strict equality operators

Use === and !== instead of == and != to avoid type coercion issues.

-    if (cachedAuthData.value == null || forceCheck == true) {
+    if (cachedAuthData.value === null || forceCheck === true) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/githubAuth.ts` at line 49, Change the loose equality checks to strict
ones in the conditional that reads "if (cachedAuthData.value == null ||
forceCheck == true)"; specifically use "===" and "!==" (e.g., check
cachedAuthData.value === null or cachedAuthData.value === undefined as
appropriate, and use forceCheck === true or simply forceCheck) so the condition
avoids type coercion; update the conditional that references
cachedAuthData.value and forceCheck accordingly.

32-34: Use const for variables that are never reassigned

now, ttl, and fiveMinsAgo are never reassigned after initialization.

-  let now = Date.now();
-  let ttl = now + (5 * 60 * 1000);
-  let fiveMinsAgo = now - (5 * 60 * 1000);
+  const now = Date.now();
+  const ttl = now + (5 * 60 * 1000);
+  const fiveMinsAgo = now - (5 * 60 * 1000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/githubAuth.ts` around lines 32 - 34, The variables now, ttl, and
fiveMinsAgo in lib/githubAuth.ts are never reassigned; change their declarations
from let to const to signal immutability and avoid accidental reassignment —
update the lines that declare now, ttl, and fiveMinsAgo accordingly (e.g.,
replace "let now", "let ttl", "let fiveMinsAgo" with "const now", "const ttl",
"const fiveMinsAgo").
api/badges.ts (1)

203-229: Inconsistent error handling pattern for hcbDonateButton

Line 205 uses apiReqData.query without the || {} fallback that other handlers use. While this might work due to zod validation, it's inconsistent with the defensive pattern at lines 53 and 129. Additionally, the 404 case returns c.notFound() which produces an HTML/text response instead of an SVG badge, breaking consistency with other endpoints.

♻️ Suggested consistency improvements
   override async handle(c: Context) {
     const apiReqData = await this.getValidatedData<typeof this.schema>();
-    const { org, style } = apiReqData.query;
+    const { org, style } = apiReqData?.query || {};
     const dbData = (await getBadgeData("hcb", "donate")).result?.data;
     const { result, code } = await getOrgData(org || "hq");
     if (code == 200) {
       // ... existing code
     } else if (code == 404) {
-      return c.notFound();
+      const badgeSvg = makeBadge({
+        label: "HCB donate for unknown organization",
+        labelColor: "EC3750",
+        message: "not found",
+        logoBase64: await resolveBadgeIcon("hcb-dark"),
+        style: validateBadgeStyle(style)
+      });
+      return c.newResponse(badgeSvg, 404, {
+        "Content-Type": "image/svg+xml",
+        "Cache-Control": "max-age=300",
+      });
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/badges.ts` around lines 203 - 229, The handler's code in handle uses
apiReqData.query directly (const { org, style } = apiReqData.query) and returns
c.notFound() on a 404 which yields non-SVG output; change to destructure from a
safe fallback (e.g., const { org, style } = apiReqData.query || {}) to match
other handlers and avoid runtime errors, and replace the c.notFound() branch
with returning a consistent SVG error badge (use makeBadge with an error Format
and the same SVG headers and caching) so the endpoint always responds with an
SVG; ensure you use the same helpers shown (getOrgData, makeBadge,
validateBadgeStyle, resolveBadgeIcon) when constructing the error badge.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/self-hosting.md`:
- Around line 44-45: The docs incorrectly instruct users to run "deno run start"
even though "start" is defined as a task in deno.json; update the command in the
sentence referencing dotenvx so it uses "deno task start" (keeping the dotenvx
wrapper: dotenvx run -f .env.prod -- deno task start) and ensure the text still
instructs to point the reverse proxy to PORT; reference the deno.json "start"
task and the docs sentence that currently contains "deno run start" when making
the change.

In `@lib/githubAuth.ts`:
- Around line 10-11: The userAgent template literal contains invalid syntax
"$({config.homepage})" causing a runtime error; fix the userAgent property in
the GitHub auth setup by replacing that fragment with a proper template
placeholder for config.homepage (use ${config.homepage}) so the userAgent string
interpolates correctly; locate the userAgent key in lib/githubAuth.ts and update
the template literal that references config.homepage.
- Around line 161-165: hashToken is incorrectly passing the supplied token into
getValidatedGithubToken (which takes no args and reads GITHUB_TOKEN), causing
the wrong token to be hashed; update hashToken to use the function parameter
when present and fall back to getValidatedGithubToken() only when token is
undefined/empty, then pass that resulting token into
msgBuffer/crypto.subtle.digest and encodeHex (retain function names hashToken,
getValidatedGithubToken, msgBuffer, encodeHex).

In `@lib/logos.ts`:
- Around line 43-51: The function simpleIconLookup validates iconName
startsWith("si:") but passes the prefixed value to resolveSimpleIcon, causing
paths like icons/si:github.svg; fix by stripping the "si:" prefix before calling
resolveSimpleIcon (e.g., derive a strippedName from iconName such as
iconName.replace(/^si:/, "") or slice(3)) and pass strippedName to
resolveSimpleIcon while keeping the original iconName for the KV cache key
(cacheKv) and cacheBuster logic so cache lookup/storage behavior remains
unchanged.
- Around line 32-34: The Data URL is being built from decoded SVG text which
corrupts the base64 payload; instead either use the original base64 string from
response.data.content directly when forming the Data URL (replace
svgContentEncoded with response.data.content when creating result) or, if you
must modify the SVG, decode and then re-encode via Buffer to base64 before
embedding (ensure svgContentEncoded is a base64 string, not UTF-8). Update the
code around the svgContentEncoded/result variables in lib/logos.ts (where
response.data.content is read) accordingly.

In `@main.ts`:
- Around line 85-108: The middleware currently bypasses token validation for the
"/admin/auth-test" path by returning true inside the bearerAuth verifyToken
handler; remove that special-case so bearerAuth always calls handleGitHubAuth
(i.e., delete the if (c.req.path == "/admin/auth-test") return true branch) so
invalid/missing tokens trigger invalidTokenMessage, or if you must keep an
unauthenticated status endpoint implement it as a separate route (not inside
bearerAuth) or replace the bypass with a minimal check (e.g., verify an
Authorization header exists via c.req.headers.get("authorization") or call
handleGitHubAuth(token, true, forceCheckPerms) and return its result) to avoid
allowing unauthenticated requests to reach the handler.

In `@utils/badges-admin.ts`:
- Around line 105-107: BadgeData currently requires message and style but the
redirect payload only sets redirectUrl; change BadgeData into a discriminated
union (e.g., variants like { kind: 'redirect'; redirectUrl: string } | { kind:
'badge'; message: string; style: string; ... }) in lib/db.ts and update the
construction in utils/badges-admin.ts to create the redirect variant (include
the discriminant field, e.g., kind: 'redirect', and only redirectUrl), then
adjust any consumers to switch on the discriminant (kind) instead of assuming
message/style exist.

---

Outside diff comments:
In `@utils/badges-admin.ts`:
- Around line 101-115: When options.type === "redirect" abort early if
options.url is undefined by logging the error and returning/throwing before
building BadgeData or calling db.setBadgeData; ensure you reference options.url,
options.type and the BadgeData shape. After calling db.setBadgeData(project,
name, "redirect", data) check result.ok and on false log an error (including
result.error or result message) and exit non-zero (or throw) instead of silently
continuing; on true keep the existing logger.success path. This touches the
redirect branch in utils/badges-admin.ts, specifically the code around
db.setBadgeData, BadgeData, logger.error, and logger.success.

---

Minor comments:
In `@api/admin.ts`:
- Around line 133-143: The catch block in the API admin handler is unsafely
accessing error.response.data and error.status; update the catch (error)
handling in api/admin.ts (the block that returns the GITHUB_API_ERROR payload)
to narrow and safely extract those fields—check if error is an object and has
response and response.data (or use optional chaining like error?.response?.data)
and a numeric status (or fallback to null/unknown), then populate ghApiResult
and ghApiStatusCode with those safe values (and optionally include the raw error
in a debug log) instead of directly accessing properties that may not exist.

In `@api/badges.ts`:
- Around line 127-161: The handle method in hcbTotalRaised (override async
handle(c: Context)) only returns responses for code == 200 and code == 404,
leaving other getOrgData(result).code paths to return undefined; add a final
fallback branch that handles all other status codes (e.g., else or default) and
returns a proper SVG badge response or an error response so the function always
returns a Response. Use the same pattern as the existing branches: build a badge
via makeBadge (with resolveBadgeIcon("hcb-dark") and validateBadgeStyle(style)
if needed), and return c.newResponse(badgeSvg, <appropriateStatus>, {
"Content-Type": "image/svg+xml", "Cache-Control": "max-age=300" }) to ensure
every execution path returns a response.
- Around line 51-85: The handle method in the class fails to return for status
codes other than 200 and 404; update the method (inside handle and where
getOrgData is called) to handle all other responses by returning a sensible
response: create a default badge or JSON error response that includes the
numeric code and a brief message, use the same headers ("Content-Type":
"image/svg+xml", "Cache-Control": "max-age=300") if you return an SVG badge (or
appropriate headers for JSON), and ensure every code path ends with return
c.newResponse(..., code, headers). Locate the logic around getOrgData(...) and
the existing if (code == 200) / else if (code == 404) branches and add a final
else that constructs and returns the fallback response using the incoming style
and resolveBadgeIcon("hcb-dark")/validateBadgeStyle as appropriate.
- Line 88: Rename the misspelled class hcbTotalRasied to hcbTotalRaised and
update all references/imports accordingly (e.g., the import in main.ts) so the
OpenAPI schema and any consumers use the corrected class name; ensure the
exported name and any places that instantiate or import hcbTotalRasied are
updated to hcbTotalRaised.

In `@docs/api-terms.md`:
- Line 34: Fix the spelling in the markdown header: change the header text
"Addeumn: Unofficial Hack Club badges" to "Addendum: Unofficial Hack Club
badges" so the section title uses the correct word ("Addendum").
- Around line 38-39: Replace the misspelled word "affliated" with the correct
spelling "affiliated" in the terms text (look for the sentence containing "you
agree to only use them in connection with your personal projects or hackathons
and events affliated with Hack Club HQ."); update that token so the sentence
reads "...events affiliated with Hack Club HQ."
- Around line 15-17: Fix the spelling typo in the API terms paragraph: replace
the incorrect word "stablize" with "stabilize" in the sentence "We may change
the API terms as we implement more features and stablize things..." in
docs/api-terms.md; also scan the file for any other occurrences of "stablize"
and correct them to "stabilize".

In `@docs/README.md`:
- Around line 3-4: Fix the typo in the README intro sentence: change the word
"exisitng" to "existing" in the first paragraph ("Welcome to the Markdown
documentation for our Badges API! ... expand exisitng content.") so the sentence
reads "... expand existing content.".

In `@docs/self-hosting.md`:
- Around line 16-17: Fix the typo in the description for the environment
variable DENO_KV_ACCESS_TOKEN by changing "authenicate" to "authenticate" in the
docs text where the variable is described; ensure the surrounding sentence
remains grammatically correct and update any other occurrences of the
misspelling in the same file or nearby lines if present.

In `@lib/db.ts`:
- Around line 107-109: In lib/db.ts replace the incorrect "throw Error(error)"
in the catch block with either re-throwing the original error (throw error) to
preserve the original stack, or if you need added context create a new Error
using "new Error(message, { cause: error })" so the original error is preserved
as the cause and the stack is retained; locate the catch block where
Error(error) is called and apply one of these fixes.

In `@lib/githubAuth.ts`:
- Around line 156-159: Update the JSDoc for the token hashing function in
lib/githubAuth.ts to fix the typo: replace "SHA-215" with "SHA-512" so the
documentation matches the implementation (the JSDoc block starting "Hash a
GitHub token into SHA-215 encoded text." should read SHA-512).

In `@lib/logos.ts`:
- Around line 59-61: The catch block currently does "throw new Error(error)"
which coerces the error object and loses message/stack; update the catch in
lib/logos.ts to rethrow the original error (e.g., "throw error") or, if you need
to add context, wrap it using Error's cause option (e.g., "throw new
Error('failed to ...', { cause: error })") so the original error details are
preserved for debugging.

In `@lib/metadata.ts`:
- Line 11: Fix the typos in the public-facing metadata strings in
lib/metadata.ts: change "communties" to "communities" in the string "HCB badges
for use by organizations and communties fiscally sponsored by Hack Club through
the HCB platform." and change any "local host" occurrences to "localhost" (also
present elsewhere around the same file, e.g., the second occurrence near line
39). Update these literal strings so the exported metadata values (the
description entries) are corrected.

In `@main.ts`:
- Around line 37-41: The condition uses loose equality on config.flags.edgeCache
which can be a string; update the check to use strict boolean logic or normalize
the flag to a boolean in your config loader: either change the condition to use
Boolean(config.flags.edgeCache) (or config.flags.edgeCache === true) when
deciding to enable edge cache for app.get("/hcb/*", edgeCache(...)) and
app.get("/badges/*", edgeCache(...)), or better convert config.flags.edgeCache
to a real boolean in lib/config.ts so callers (like the if in main.ts) can
simply test the boolean value.

In `@README.md`:
- Around line 32-36: Update the README local/self-hosting text to fix two typos:
change "overriden" to "overridden" in the sentence mentioning PORT and change "a
more detailed instructions" to "more detailed instructions" in the "See the
docs" line (the line referencing "localhost:8080", "PORT", and
"./docs/self-hosting.md"). Ensure the corrected phrases appear exactly where
those original tokens are used.

In `@utils/manage-api-users.ts`:
- Around line 104-105: The comparison in utils/manage-api-users.ts is using a
string literal for HTTP status; change the check that currently inspects
err.status == "404" to compare against a number (e.g. err.status === 404) or
coerce the value to a number before comparing so the condition correctly detects
a 404; update the branch that calls logger.warn(`user ${username} is not in
organization`) to use the numeric comparison (or Number(err.status) === 404) so
the warning fires as intended.
- Around line 154-155: The code is comparing err.status to the string "404"
which is the wrong type; update the error check in the function handling API
user membership (the block that currently reads if (err.status == "404") {
logger.warn(`membership status: not in team or org`); }) to compare against a
numeric status (e.g. err.status === 404) or normalize the value first (e.g.
parseInt(err.status) === 404) and keep the existing logger.warn call; ensure you
also handle other truthy/undefined cases safely by checking for err.status or
err.statusCode consistently.

---

Nitpick comments:
In `@api/admin.ts`:
- Line 40: The conditional in the admin auth check uses loose equality ("if
(parsedAuthHeader[1] == \"null\")")—change it to strict equality using === to
avoid type-coercion bugs; update the condition in the same auth-handling block
(where parsedAuthHeader is inspected) to use "parsedAuthHeader[1] === 'null'".
- Around line 147-157: The three OpenAPIRoute classes revokeAdminAccess,
getAdminUserInfo, and listAdmins are empty stubs and must either implement route
metadata and handlers or be explicitly disabled; implement for each a static
schema() method (HTTP method, path params/body/query schemas, responses) and an
async handle(req, res) that validates input, calls the appropriate service
(e.g., AdminService.revoke, AdminService.getInfo, AdminService.list), and
returns proper HTTP status codes and error handling, or if you prefer to block
registration throw/return a clear "NotImplemented" error in handle to prevent
runtime routing errors; reference the class names revokeAdminAccess,
getAdminUserInfo, and listAdmins when making the changes.

In `@api/badges.ts`:
- Around line 203-229: The handler's code in handle uses apiReqData.query
directly (const { org, style } = apiReqData.query) and returns c.notFound() on a
404 which yields non-SVG output; change to destructure from a safe fallback
(e.g., const { org, style } = apiReqData.query || {}) to match other handlers
and avoid runtime errors, and replace the c.notFound() branch with returning a
consistent SVG error badge (use makeBadge with an error Format and the same SVG
headers and caching) so the endpoint always responds with an SVG; ensure you use
the same helpers shown (getOrgData, makeBadge, validateBadgeStyle,
resolveBadgeIcon) when constructing the error badge.

In `@lib/db.ts`:
- Line 97: The variable declaration "let result: string | null" is missing a
trailing semicolon; update the declaration of the variable named result (the
line containing "let result: string | null") to include a semicolon at the end
so the TypeScript syntax is correct.
- Around line 98-99: The comparison uses loose equality (icon == null) which
allows type coercion; replace it with a strict check—either use icon === null if
you only want to detect null, or explicitly check both absence cases with icon
=== null || icon === undefined to preserve the current semantics; update the
conditional around the icon variable (the if handling the icon value)
accordingly so behavior remains correct and uses strict equality.

In `@lib/githubAuth.ts`:
- Line 49: Change the loose equality checks to strict ones in the conditional
that reads "if (cachedAuthData.value == null || forceCheck == true)";
specifically use "===" and "!==" (e.g., check cachedAuthData.value === null or
cachedAuthData.value === undefined as appropriate, and use forceCheck === true
or simply forceCheck) so the condition avoids type coercion; update the
conditional that references cachedAuthData.value and forceCheck accordingly.
- Around line 32-34: The variables now, ttl, and fiveMinsAgo in
lib/githubAuth.ts are never reassigned; change their declarations from let to
const to signal immutability and avoid accidental reassignment — update the
lines that declare now, ttl, and fiveMinsAgo accordingly (e.g., replace "let
now", "let ttl", "let fiveMinsAgo" with "const now", "const ttl", "const
fiveMinsAgo").

In `@utils/types.ts`:
- Line 1: Replace the value-level import of BadgeData with a type-only import to
avoid runtime coupling: change the import of BadgeData in utils/types.ts to a
type import (use the symbol BadgeData and the module ../lib/db.ts) so the
compiler treats it only as a type and does not retain a runtime dependency on
lib/db.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3b6ceefb-8ba2-4d12-9e71-50730bd83768

📥 Commits

Reviewing files that changed from the base of the PR and between c4f36f9 and 0c90caf.

📒 Files selected for processing (21)
  • .vscode/settings.json
  • CONTRIBUTING.md
  • README.md
  • api/admin.ts
  • api/badges.ts
  • docs/README.md
  • docs/api-terms.md
  • docs/deno-scripts.md
  • docs/self-hosting.md
  • lib/config.ts
  • lib/db.ts
  • lib/githubAuth.ts
  • lib/hcb.ts
  • lib/logos.ts
  • lib/metadata.ts
  • lib/utils.ts
  • main.ts
  • utils/badges-admin.ts
  • utils/manage-api-users.ts
  • utils/preseed-kv.ts
  • utils/types.ts
✅ Files skipped from review due to trivial changes (2)
  • lib/hcb.ts
  • CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/utils.ts
  • lib/config.ts
  • .vscode/settings.json

Comment thread docs/self-hosting.md
Comment on lines +44 to +45
6. Otherwise, just run `dotenvx run -f .env.prod -- deno run start` and point your reverse
proxy to `PORT`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether start/dev are defined as tasks and compare doc commands.
fd 'deno.json$' -x sh -c 'echo ">>> $1"; rg -n "\"scripts\"|\"start\"|\"dev\"|\"deploy\"" "$1"'
rg -n -C2 'deno run start|deno task start|deno task dev' docs/self-hosting.md docs/deno-scripts.md README.md

Repository: lorebooks-wiki/badges-api

Length of output: 639


🏁 Script executed:

fd -type f 'deno\.json' -o -name 'deno\.jsonc'

Repository: lorebooks-wiki/badges-api

Length of output: 239


🏁 Script executed:

rg -n '\"start\"' --type json

Repository: lorebooks-wiki/badges-api

Length of output: 144


🏁 Script executed:

fd --type f 'deno\.json'

Repository: lorebooks-wiki/badges-api

Length of output: 78


🏁 Script executed:

rg 'start' --type json -A 2 -B 2

Repository: lorebooks-wiki/badges-api

Length of output: 557


Line 44 uses the wrong Deno command (deno run start).

Since start is defined as a task in deno.json, the correct command is deno task start. Otherwise, the command will fail for self-hosters.

Suggested fix
-6. Otherwise, just run `dotenvx run -f .env.prod -- deno run start` and point your reverse
+6. Otherwise, just run `dotenvx run -f .env.prod -- deno task start` and point your reverse
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
6. Otherwise, just run `dotenvx run -f .env.prod -- deno run start` and point your reverse
proxy to `PORT`.
6. Otherwise, just run `dotenvx run -f .env.prod -- deno task start` and point your reverse
proxy to `PORT`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/self-hosting.md` around lines 44 - 45, The docs incorrectly instruct
users to run "deno run start" even though "start" is defined as a task in
deno.json; update the command in the sentence referencing dotenvx so it uses
"deno task start" (keeping the dotenvx wrapper: dotenvx run -f .env.prod -- deno
task start) and ensure the text still instructs to point the reverse proxy to
PORT; reference the deno.json "start" task and the docs sentence that currently
contains "deno run start" when making the change.

Comment thread lib/githubAuth.ts
Comment on lines +10 to +11
userAgent: `@lorebooks-wiki/badges-api $({config.homepage})`
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Syntax error in userAgent string template literal

The string template uses $({config.homepage}) which is invalid syntax. This will cause a runtime error.

🐛 Proposed fix
 export const github = (token: string) => new Octokit({
   auth: token,
-  userAgent: `@lorebooks-wiki/badges-api $({config.homepage})`
+  userAgent: `@lorebooks-wiki/badges-api (${config.homepage})`
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
userAgent: `@lorebooks-wiki/badges-api $({config.homepage})`
})
userAgent: `@lorebooks-wiki/badges-api (${config.homepage})`
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/githubAuth.ts` around lines 10 - 11, The userAgent template literal
contains invalid syntax "$({config.homepage})" causing a runtime error; fix the
userAgent property in the GitHub auth setup by replacing that fragment with a
proper template placeholder for config.homepage (use ${config.homepage}) so the
userAgent string interpolates correctly; locate the userAgent key in
lib/githubAuth.ts and update the template literal that references
config.homepage.

Comment thread lib/githubAuth.ts
Comment on lines +161 to +165
export async function hashToken(token: string) {
const validatedToken = getValidatedGithubToken(token)
const msg = msgBuffer(validatedToken);
const hashBuff = await crypto.subtle.digest("SHA-512", msg);
return encodeHex(hashBuff);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getValidatedGithubToken signature in lib/config.ts
ast-grep --pattern 'export function getValidatedGithubToken($$$)'

Repository: lorebooks-wiki/badges-api

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Search for getValidatedGithubToken function definition
rg "getValidatedGithubToken" --max-count 20

Repository: lorebooks-wiki/badges-api

Length of output: 352


🏁 Script executed:

#!/bin/bash
# List files in lib directory to understand structure
ls -la lib/

Repository: lorebooks-wiki/badges-api

Length of output: 643


🏁 Script executed:

#!/bin/bash
# Check if config.ts exists and read it
fd "config.ts" -t f

Repository: lorebooks-wiki/badges-api

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Read lib/config.ts to see the actual function implementation
cat -n lib/config.ts

Repository: lorebooks-wiki/badges-api

Length of output: 1483


🏁 Script executed:

#!/bin/bash
# Read lib/githubAuth.ts focusing on the hashToken function around line 161-165
sed -n '155,170p' lib/githubAuth.ts

Repository: lorebooks-wiki/badges-api

Length of output: 443


getValidatedGithubToken does not accept parameters—hashing the wrong token

getValidatedGithubToken() is defined with no parameters and reads from the GITHUB_TOKEN environment variable. Calling it as getValidatedGithubToken(token) ignores the supplied token and hashes the service token instead, breaking authentication.

🐛 Proposed fix
 export async function hashToken(token: string) {
-  const validatedToken = getValidatedGithubToken(token)
-  const msg = msgBuffer(validatedToken);
+  const msg = msgBuffer(token);
   const hashBuff = await crypto.subtle.digest("SHA-512", msg);
   return encodeHex(hashBuff);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function hashToken(token: string) {
const validatedToken = getValidatedGithubToken(token)
const msg = msgBuffer(validatedToken);
const hashBuff = await crypto.subtle.digest("SHA-512", msg);
return encodeHex(hashBuff);
export async function hashToken(token: string) {
const msg = msgBuffer(token);
const hashBuff = await crypto.subtle.digest("SHA-512", msg);
return encodeHex(hashBuff);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/githubAuth.ts` around lines 161 - 165, hashToken is incorrectly passing
the supplied token into getValidatedGithubToken (which takes no args and reads
GITHUB_TOKEN), causing the wrong token to be hashed; update hashToken to use the
function parameter when present and fall back to getValidatedGithubToken() only
when token is undefined/empty, then pass that resulting token into
msgBuffer/crypto.subtle.digest and encodeHex (retain function names hashToken,
getValidatedGithubToken, msgBuffer, encodeHex).

Comment thread lib/logos.ts
Comment on lines +32 to +34
const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
return result
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect base64 encoding produces corrupted Data URL

response.data.content from GitHub API is already base64-encoded. The current code decodes it to UTF-8 SVG text, then embeds that raw text in the Data URL without re-encoding it. This produces an invalid Data URL.

Either keep the original base64 content directly, or re-encode after decoding:

🐛 Option 1: Use the original base64 directly (simpler)
-    const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
-    const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
+    // content is already base64-encoded from GitHub API
+    const base64Content = response.data.content.replace(/\n/g, ''); // Remove any line breaks
+    const result = `data:image/svg+xml;base64,${base64Content}`;
🐛 Option 2: Decode then re-encode (if you need to process the SVG)
-    const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
-    const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
+    const svgContent = Buffer.from(response.data.content, "base64").toString("utf-8");
+    const reEncoded = Buffer.from(svgContent, "utf-8").toString("base64");
+    const result = `data:image/svg+xml;base64,${reEncoded}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
return result
const base64Content = response.data.content.replace(/\n/g, '');
const result = `data:image/svg+xml;base64,${base64Content}`;
return result
Suggested change
const svgContentEncoded = Buffer.from(response.data.content, "base64").toString("utf-8");
const result = `data:image/svg+xml;base64,${svgContentEncoded}`;
return result
const svgContent = Buffer.from(response.data.content, "base64").toString("utf-8");
const reEncoded = Buffer.from(svgContent, "utf-8").toString("base64");
const result = `data:image/svg+xml;base64,${reEncoded}`;
return result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/logos.ts` around lines 32 - 34, The Data URL is being built from decoded
SVG text which corrupts the base64 payload; instead either use the original
base64 string from response.data.content directly when forming the Data URL
(replace svgContentEncoded with response.data.content when creating result) or,
if you must modify the SVG, decode and then re-encode via Buffer to base64
before embedding (ensure svgContentEncoded is a base64 string, not UTF-8).
Update the code around the svgContentEncoded/result variables in lib/logos.ts
(where response.data.content is read) accordingly.

Comment thread lib/logos.ts
Comment on lines +43 to +51
if (!iconName.startsWith("si:")) {
throw new Error("not a simple-icon icon name")
}
}

return svg2base64(iconSvg);
}

function prepareNamedLogo({ name, color, style, size }) {
if (typeof name !== "string") {
return undefined;
}
// check on our KV cache first
const cacheKv = await kvApi.get<string>(["badgeIcons", iconName])

name = name.toLowerCase();
// if not cached OR the cache busting is enabled, run the resolver
if (cacheKv.value === null || cacheBuster === true) {
const upstreamData = await resolveSimpleIcon(iconName)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

si: prefix not stripped before calling resolveSimpleIcon

simpleIconLookup validates that iconName starts with si: but passes the full name (including prefix) to resolveSimpleIcon. This results in an invalid path like icons/si:github.svg instead of icons/github.svg.

🐛 Proposed fix
     if (!iconName.startsWith("si:")) {
       throw new Error("not a simple-icon icon name")
     }
+    // Strip the "si:" prefix for the actual icon name
+    const actualIconName = iconName.replace(/^si:/, '');
     // check on our KV cache first
     const cacheKv = await kvApi.get<string>(["badgeIcons", iconName])
 
     // if not cached OR the cache busting is enabled, run the resolver
     if (cacheKv.value === null || cacheBuster === true) {
-      const upstreamData = await resolveSimpleIcon(iconName)
+      const upstreamData = await resolveSimpleIcon(actualIconName)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!iconName.startsWith("si:")) {
throw new Error("not a simple-icon icon name")
}
}
return svg2base64(iconSvg);
}
function prepareNamedLogo({ name, color, style, size }) {
if (typeof name !== "string") {
return undefined;
}
// check on our KV cache first
const cacheKv = await kvApi.get<string>(["badgeIcons", iconName])
name = name.toLowerCase();
// if not cached OR the cache busting is enabled, run the resolver
if (cacheKv.value === null || cacheBuster === true) {
const upstreamData = await resolveSimpleIcon(iconName)
if (!iconName.startsWith("si:")) {
throw new Error("not a simple-icon icon name")
}
// Strip the "si:" prefix for the actual icon name
const actualIconName = iconName.replace(/^si:/, '');
// check on our KV cache first
const cacheKv = await kvApi.get<string>(["badgeIcons", iconName])
// if not cached OR the cache busting is enabled, run the resolver
if (cacheKv.value === null || cacheBuster === true) {
const upstreamData = await resolveSimpleIcon(actualIconName)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/logos.ts` around lines 43 - 51, The function simpleIconLookup validates
iconName startsWith("si:") but passes the prefixed value to resolveSimpleIcon,
causing paths like icons/si:github.svg; fix by stripping the "si:" prefix before
calling resolveSimpleIcon (e.g., derive a strippedName from iconName such as
iconName.replace(/^si:/, "") or slice(3)) and pass strippedName to
resolveSimpleIcon while keeping the original iconName for the KV cache key
(cacheKv) and cacheBuster logic so cache lookup/storage behavior remains
unchanged.

Comment thread main.ts
Comment on lines +85 to +108
app.use("/admin/*", bearerAuth({
verifyToken: async (token: string, c: Context) => {
const forceCheckPerms = Boolean(c.req.query("force")) || false
if (c.req.path == "/admin/auth-test") {
return true // skip validating the token on auth-test endpoint
} else {
return await handleGitHubAuth(token, true, forceCheckPerms)
}
},
invalidAuthenticationHeaderMessage: {
ok: false,
error: {
code: "MISSING_AUTH",
message: "Authorization header is required"
}
},
invalidTokenMessage: {
ok: false,
error: {
code: "AUTH_ERROR",
message: "Your API token is invalid"
}
}
}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Security concern: /admin/auth-test bypasses bearer auth validation

Returning true unconditionally at line 89 allows any request (including invalid tokens or no token) to reach the /admin/auth-test handler. While the handler performs its own validation, this:

  1. Bypasses the middleware's invalidTokenMessage response for this endpoint
  2. Allows unauthenticated requests to consume handler resources and potentially trigger GitHub API calls

Consider removing the bypass or using a lighter validation check.

🛡️ Suggested approach

Option 1: Remove the bypass entirely - let the middleware validate:

   verifyToken: async (token: string, c: Context) => {
     const forceCheckPerms = Boolean(c.req.query("force")) || false
-    if (c.req.path == "/admin/auth-test") {
-      return true // skip validating the token on auth-test endpoint
-    } else {
-      return await handleGitHubAuth(token, true, forceCheckPerms)
-    }
+    return await handleGitHubAuth(token, true, forceCheckPerms)
   },

Option 2: If you want auth-test to show auth status for any token (even invalid), consider a separate non-authenticated endpoint.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use("/admin/*", bearerAuth({
verifyToken: async (token: string, c: Context) => {
const forceCheckPerms = Boolean(c.req.query("force")) || false
if (c.req.path == "/admin/auth-test") {
return true // skip validating the token on auth-test endpoint
} else {
return await handleGitHubAuth(token, true, forceCheckPerms)
}
},
invalidAuthenticationHeaderMessage: {
ok: false,
error: {
code: "MISSING_AUTH",
message: "Authorization header is required"
}
},
invalidTokenMessage: {
ok: false,
error: {
code: "AUTH_ERROR",
message: "Your API token is invalid"
}
}
}))
app.use("/admin/*", bearerAuth({
verifyToken: async (token: string, c: Context) => {
const forceCheckPerms = Boolean(c.req.query("force")) || false
return await handleGitHubAuth(token, true, forceCheckPerms)
},
invalidAuthenticationHeaderMessage: {
ok: false,
error: {
code: "MISSING_AUTH",
message: "Authorization header is required"
}
},
invalidTokenMessage: {
ok: false,
error: {
code: "AUTH_ERROR",
message: "Your API token is invalid"
}
}
}))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.ts` around lines 85 - 108, The middleware currently bypasses token
validation for the "/admin/auth-test" path by returning true inside the
bearerAuth verifyToken handler; remove that special-case so bearerAuth always
calls handleGitHubAuth (i.e., delete the if (c.req.path == "/admin/auth-test")
return true branch) so invalid/missing tokens trigger invalidTokenMessage, or if
you must keep an unauthenticated status endpoint implement it as a separate
route (not inside bearerAuth) or replace the bypass with a minimal check (e.g.,
verify an Authorization header exists via c.req.headers.get("authorization") or
call handleGitHubAuth(token, true, forceCheckPerms) and return its result) to
avoid allowing unauthenticated requests to reach the handler.

Comment thread utils/badges-admin.ts
Comment on lines +105 to 107
const data: BadgeData = {
redirectUrl: options.url,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Inspect BadgeData shape
rg -n -A20 -B2 '^export type BadgeData' lib/db.ts

# 2) Inspect setBadgeData signature
rg -n -A12 -B2 '^export async function setBadgeData' lib/db.ts

# 3) Inspect redirect payload assignment in CLI
rg -n -A10 -B4 'if \(options\.type == "redirect"\)' utils/badges-admin.ts

Repository: lorebooks-wiki/badges-api

Length of output: 1677


BadgeData type mismatch: required message and style fields missing for redirect payload.

The BadgeData type in lib/db.ts requires message and style fields, but the redirect payload at lines 105-107 only provides redirectUrl. This creates a type-model mismatch. Consider using discriminated union types to have separate payload shapes for redirect vs. badge types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/badges-admin.ts` around lines 105 - 107, BadgeData currently requires
message and style but the redirect payload only sets redirectUrl; change
BadgeData into a discriminated union (e.g., variants like { kind: 'redirect';
redirectUrl: string } | { kind: 'badge'; message: string; style: string; ... })
in lib/db.ts and update the construction in utils/badges-admin.ts to create the
redirect variant (include the discriminant field, e.g., kind: 'redirect', and
only redirectUrl), then adjust any consumers to switch on the discriminant
(kind) instead of assuming message/style exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants