Skip to content

Commit 3637253

Browse files
Dathan Vance Pattishallclaude
andcommitted
fix: add missing frontend/src/api/client.ts and unbreak Docker build
The bare `api` rule in .gitignore was matching frontend/src/api/, causing client.ts to be excluded from the repo. The Docker build failed because AuthContext, Admin, AgentChat, Login, and Payment all import from ../api/client. - Root-anchor the gitignore rules (/api, /bin) so only top-level dirs are excluded - Commit frontend/src/api/client.ts so the frontend build has the file it needs - Add .github/dependabot.yml, security.yml, and SECURITY.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 553d1f2 commit 3637253

File tree

5 files changed

+288
-3
lines changed

5 files changed

+288
-3
lines changed

.github/dependabot.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
version: 2
2+
updates:
3+
# Go modules
4+
- package-ecosystem: "gomod"
5+
directory: "/"
6+
schedule:
7+
interval: "daily"
8+
open-pull-requests-limit: 10
9+
reviewers:
10+
- "dathan"
11+
labels:
12+
- "dependencies"
13+
- "go"
14+
15+
# npm (frontend)
16+
- package-ecosystem: "npm"
17+
directory: "/frontend"
18+
schedule:
19+
interval: "daily"
20+
open-pull-requests-limit: 10
21+
reviewers:
22+
- "dathan"
23+
labels:
24+
- "dependencies"
25+
- "npm"
26+
27+
# Docker base images
28+
- package-ecosystem: "docker"
29+
directory: "/"
30+
schedule:
31+
interval: "weekly"
32+
reviewers:
33+
- "dathan"
34+
labels:
35+
- "dependencies"
36+
- "docker"
37+
38+
# GitHub Actions
39+
- package-ecosystem: "github-actions"
40+
directory: "/"
41+
schedule:
42+
interval: "weekly"
43+
reviewers:
44+
- "dathan"
45+
labels:
46+
- "dependencies"
47+
- "github-actions"

.github/workflows/security.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Security
2+
3+
on:
4+
# Fires when a GitHub Security Advisory is published for this repo
5+
# (includes privately reported vulnerabilities once triaged).
6+
repository_vulnerability_alert:
7+
types: [create]
8+
9+
# Fires when Dependabot opens or updates a PR.
10+
pull_request:
11+
types: [opened, synchronize, reopened]
12+
13+
permissions:
14+
contents: write # needed to merge Dependabot PRs
15+
issues: write # needed to open issues
16+
pull-requests: write # needed to approve / merge PRs
17+
18+
jobs:
19+
# ── Open a tracking issue when a new vulnerability alert is created ──────────
20+
open-security-issue:
21+
if: github.event_name == 'repository_vulnerability_alert'
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Create tracking issue
25+
uses: actions/github-script@v7
26+
with:
27+
script: |
28+
const alert = context.payload.alert;
29+
const pkg = alert.affected_package_name || 'unknown package';
30+
const sev = alert.severity || 'unknown severity';
31+
const url = alert.html_url || '';
32+
33+
await github.rest.issues.create({
34+
owner: context.repo.owner,
35+
repo: context.repo.repo,
36+
title: `[Security] Vulnerability in ${pkg} (${sev})`,
37+
body: [
38+
`## Vulnerability Alert`,
39+
``,
40+
`**Package:** ${pkg}`,
41+
`**Severity:** ${sev}`,
42+
`**Advisory:** ${url}`,
43+
``,
44+
`Dependabot will open a pull request to patch this dependency automatically.`,
45+
`This issue will be closed once the fix is merged.`,
46+
].join('\n'),
47+
labels: ['security', 'dependencies'],
48+
assignees: ['dathan'],
49+
});
50+
51+
- name: Notify via GitHub (assign + watch already triggers email)
52+
run: echo "Issue created — GitHub will notify assigned users via email/web."
53+
54+
# ── Auto-merge Dependabot PRs that pass CI ───────────────────────────────────
55+
auto-merge-dependabot:
56+
if: |
57+
github.event_name == 'pull_request' &&
58+
github.actor == 'dependabot[bot]'
59+
runs-on: ubuntu-latest
60+
steps:
61+
- name: Fetch Dependabot metadata
62+
id: meta
63+
uses: dependabot/fetch-metadata@v2
64+
with:
65+
github-token: ${{ secrets.GITHUB_TOKEN }}
66+
67+
- name: Approve the PR
68+
run: gh pr review --approve "$PR_URL"
69+
env:
70+
PR_URL: ${{ github.event.pull_request.html_url }}
71+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72+
73+
- name: Enable auto-merge (squash)
74+
# Auto-merge triggers once all required status checks pass.
75+
run: gh pr merge --auto --squash "$PR_URL"
76+
env:
77+
PR_URL: ${{ github.event.pull_request.html_url }}
78+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79+
80+
- name: Notify owner of Dependabot PR
81+
uses: actions/github-script@v7
82+
with:
83+
script: |
84+
const pr = context.payload.pull_request;
85+
const meta = {
86+
name: '${{ steps.meta.outputs.dependency-names }}',
87+
from: '${{ steps.meta.outputs.previous-version }}',
88+
to: '${{ steps.meta.outputs.new-version }}',
89+
type: '${{ steps.meta.outputs.update-type }}',
90+
};
91+
await github.rest.issues.createComment({
92+
owner: context.repo.owner,
93+
repo: context.repo.repo,
94+
issue_number: pr.number,
95+
body: [
96+
`@dathan — Dependabot opened this PR:`,
97+
``,
98+
`| | |`,
99+
`|---|---|`,
100+
`| **Package** | ${meta.name} |`,
101+
`| **Update** | ${meta.from} → ${meta.to} (${meta.type}) |`,
102+
`| **Status** | Auto-merge enabled — will merge once CI passes |`,
103+
].join('\n'),
104+
});

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
# Project Dependencies
2020
vendor
2121

22-
# Ignore api dir
23-
api
24-
bin
22+
# Ignore top-level api and bin dirs (root-anchored so frontend/src/api/ is not affected)
23+
/api
24+
/bin

SECURITY.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
| Version | Supported |
6+
| ------- | --------- |
7+
| latest (`master`) | :white_check_mark: |
8+
| older tags | :x: |
9+
10+
## Reporting a Vulnerability
11+
12+
You may report a vulnerability either publicly or privately:
13+
14+
- **Public issue**[Open an issue](../../issues/new) if you are comfortable disclosing the details publicly. Use the `security` label.
15+
- **Private advisory** — Use GitHub's [private vulnerability reporting](../../security/advisories/new) if the vulnerability is sensitive and you prefer coordinated disclosure before it is publicly visible.
16+
17+
Include in your report:
18+
- A description of the vulnerability and its potential impact
19+
- Steps to reproduce or a proof-of-concept
20+
- Affected versions
21+
- Any suggested mitigations, if known
22+
23+
You can expect:
24+
- **Acknowledgement** within 2 business days
25+
- **Status update** within 7 days (confirmed, declined, or needs more info)
26+
- **Patch and disclosure** coordinated with you once a fix is ready
27+
28+
When the vulnerability is fixed, a GitHub Security Advisory will be published and a CVE will be requested if warranted.
29+
30+
## Automated Security
31+
32+
- **Dependabot** is enabled and will automatically open pull requests to upgrade vulnerable dependencies (Go modules, npm packages, Docker base images).
33+
- Dependabot PRs that pass CI are **automatically merged**.
34+
- The repository owner is notified by GitHub whenever a security advisory is published or a Dependabot PR is opened.

frontend/src/api/client.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Typed API client. All fetch calls go through here so auth headers and
2+
// base URL are applied consistently.
3+
4+
const BASE = import.meta.env.VITE_API_URL ?? "";
5+
6+
export interface User {
7+
id: string;
8+
email: string;
9+
name: string;
10+
avatar_url: string;
11+
provider: string;
12+
role: string;
13+
paid_at: string | null;
14+
created_at: string;
15+
}
16+
17+
export interface PaymentIntentResponse {
18+
client_secret: string;
19+
payment_id: string;
20+
}
21+
22+
export interface UserListResponse {
23+
users: User[];
24+
total: number;
25+
}
26+
27+
export interface AssumeResponse {
28+
token: string;
29+
target_user: User;
30+
}
31+
32+
// ── Token Management ──────────────────────────────────────────────────────────
33+
34+
const TOKEN_KEY = "jwt";
35+
36+
export const getToken = (): string | null => localStorage.getItem(TOKEN_KEY);
37+
export const setToken = (t: string) => localStorage.setItem(TOKEN_KEY, t);
38+
export const clearToken = () => localStorage.removeItem(TOKEN_KEY);
39+
40+
// ── Core Fetch ────────────────────────────────────────────────────────────────
41+
42+
async function request<T>(
43+
path: string,
44+
options: RequestInit = {}
45+
): Promise<T> {
46+
const token = getToken();
47+
const headers: Record<string, string> = {
48+
"Content-Type": "application/json",
49+
...(options.headers as Record<string, string>),
50+
};
51+
if (token) {
52+
headers["Authorization"] = `Bearer ${token}`;
53+
}
54+
55+
const res = await fetch(`${BASE}${path}`, { ...options, headers, credentials: "include" });
56+
57+
if (!res.ok) {
58+
const err = await res.json().catch(() => ({ error: res.statusText }));
59+
throw new Error(err.error ?? `HTTP ${res.status}`);
60+
}
61+
62+
if (res.status === 204) return undefined as unknown as T;
63+
return res.json();
64+
}
65+
66+
// ── Auth ──────────────────────────────────────────────────────────────────────
67+
68+
export const getMe = () => request<User>("/api/v1/me");
69+
70+
export const logout = () =>
71+
request<void>("/auth/logout", { method: "POST" }).then(clearToken);
72+
73+
export const oauthURL = (provider: string) => `${BASE}/auth/${provider}`;
74+
75+
// ── Admin ─────────────────────────────────────────────────────────────────────
76+
77+
export const listUsers = (limit = 50, offset = 0) =>
78+
request<UserListResponse>(`/api/v1/admin/users?limit=${limit}&offset=${offset}`);
79+
80+
export const assumeUser = (id: string) =>
81+
request<AssumeResponse>(`/api/v1/admin/users/${id}/assume`, { method: "POST" });
82+
83+
// ── Payments ──────────────────────────────────────────────────────────────────
84+
85+
export const createPaymentIntent = (amount: number, currency = "usd") =>
86+
request<PaymentIntentResponse>("/api/v1/payments/intent", {
87+
method: "POST",
88+
body: JSON.stringify({ amount, currency }),
89+
});
90+
91+
// ── Agent ─────────────────────────────────────────────────────────────────────
92+
93+
export const sendPrompt = (prompt: string) =>
94+
request<{ response: string }>("/api/v1/agent/prompt", {
95+
method: "POST",
96+
body: JSON.stringify({ prompt }),
97+
});
98+
99+
export const streamURL = (prompt: string) =>
100+
`${BASE}/api/v1/agent/stream?prompt=${encodeURIComponent(prompt)}`;

0 commit comments

Comments
 (0)