Skip to content

Commit 167542c

Browse files
committed
# Changelog
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added - **SDK**: Introduced `@opencodehub/sdk`, a TypeScript Client for programmatically interacting with the OpenCodeHub API. - **CLI**: Released `opencodehub-cli` (v1.0.1) to npm. - Added `och` command globally. - Implemented smoke tests for CLI verification. - **Documentation**: - Added comprehensive "Roadmap" to `README.md` for Q2-Q4 2026. - Added `docs/publishing.md` guide for CLI release management. - **Assets**: Updated project logo to SVG format. ### Changed - **Logs**: Cleaned up production code by removing debug `console.log` statements in: - `src/pages/[owner]/[repo]/milestones/index.astro` - `src/pages/[owner]/[repo]/settings/deploy-keys.astro` - `src/pages/[owner]/[repo]/wiki/index.astro` - `src/pages/[owner]/[repo]/actions/index.astro` - **Repo Links**: Updated all hardcoded GitHub repository URLs to point to `swadhinbiswas/OpencodeHub`. ### Fixed - **Security**: Fixed Critical Authorization Bypass in Issue Creation API (`issues/index.ts`). - Now correctly checks `canReadRepo` before inserting issues. - **Security**: Fixed Privacy Leak in Collaborators List API (`collaborators/index.ts`). - Added permission check to prevent unauthorized users from enumerating members of private repositories. - **CLI**: Fixed incorrect binary path in `cli/package.json` that prevented global execution. ## [0.2.0] - 2026-01-21 ### Added - **Cloudflare R2 Storage Integration**: - Full support for Cloudflare R2 as a repository storage backend. - Configure with `STORAGE_TYPE=s3` and R2-specific environment variables. - Repositories are automatically uploaded to R2 on creation. - Repositories are automatically deleted from R2 on deletion. - Git clone/push operations now work with cloud-stored repositories. - **UI Modernization**: - Added global toast notification system using `sonner` package. - Created `src/components/ui/sonner.tsx` Shadcn UI wrapper. - Added `<Toaster />` component to `BaseLayout.astro` for app-wide notifications. - New `RepoSettings.tsx` React component for repository settings management. - Replaced native `alert()` and `confirm()` with Shadcn `AlertDialog` components. - **New Pages & Features**: - `src/pages/insights.astro` - Platform-wide analytics dashboard. - `src/pages/merge-queue.astro` - Merge queue management interface. - `src/pages/settings/ai-review-rules.astro` - AI code review configuration. - `src/pages/settings/automations.astro` - Workflow automation settings. - `src/pages/settings/notification-preferences.astro` - User notification settings. - `src/pages/api/user/notification-preferences.ts` - New API endpoint. ### Changed - **Database**: Migrated all schema files to use `pgTable` with PostgreSQL types. - **API Routes**: Standardized logging and error handling across 60+ API files. - **Drizzle ORM**: Automated TypeScript casting fixes across the codebase. - **Repository Creation**: Made cloud storage sync asynchronous for faster API responses. - **Git Operations**: Refactored `initRepository` to separate cloud storage concerns from git initialization. ### Fixed - **Cloud Storage**: Fixed `S3StorageAdapter` incorrectly prepending base path to S3 keys (caused `NoSuchKey` errors). - **Cloud Storage**: Fixed `uploadRepoToStorage` uploading 0 files due to duplicate `initRepoInStorage` calls. - **Cloud Storage**: Fixed `triggerRepoWorkflows` failing with "directory does not exist" for R2-backed repos. - **Cloud Storage**: Fixed repository deletion not cleaning up objects from R2 storage. - **Git Backend**: Fixed `git clone` and `git push` operations for cloud-stored repositories. - **Git Operations**: Added `resolveRepoPath` for resolving logical cloud paths to local temp directories. - **Admin Pages**: Fixed TypeScript errors in `admin/settings.astro` and `admin/users.astro`. - **Tree Pages**: Fixed layout issues by replacing missing `RepositoryLayout.astro`. - **API**: Fixed Drizzle ORM "count" property errors in admin pages. - **Stacks API**: Fixed TypeScript type limitations. ### Security - **Postgres Support**: Full PostgreSQL support with `node-postgres` driver. - **Environment Variables**: Improved `isCloudStorage()` detection using `STORAGE_TYPE` env directly. ## [0.1.0] - 2026-01-14 ### Added - Initial release of OpenCodeHub. - Basic repo management, issue tracking, and PR workflow.
1 parent 19a5396 commit 167542c

27 files changed

Lines changed: 1673 additions & 1150 deletions

File tree

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"remark-parse": "^11.0.0",
110110
"remark-rehype": "^11.1.2",
111111
"simple-git": "^3.25.0",
112+
"sonner": "^2.0.7",
112113
"ssh2": "^1.15.0",
113114
"tailwind-merge": "^2.6.0",
114115
"tailwindcss-animate": "^1.0.7",
@@ -142,4 +143,4 @@
142143
"engines": {
143144
"node": ">=20.0.0"
144145
}
145-
}
146+
}
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import React, { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { Label } from "@/components/ui/label";
5+
import { Textarea } from "@/components/ui/textarea";
6+
import {
7+
Card,
8+
CardContent,
9+
CardDescription,
10+
CardHeader,
11+
CardTitle,
12+
CardFooter,
13+
} from "@/components/ui/card";
14+
import {
15+
AlertDialog,
16+
AlertDialogAction,
17+
AlertDialogCancel,
18+
AlertDialogContent,
19+
AlertDialogDescription,
20+
AlertDialogFooter,
21+
AlertDialogHeader,
22+
AlertDialogTitle,
23+
AlertDialogTrigger,
24+
} from "@/components/ui/alert-dialog";
25+
import { toast } from "sonner"; // Assuming toast is available, or we'll simple alerts/console for now if not set up, but ideally use toast.
26+
27+
interface RepoSettingsProps {
28+
repo: {
29+
owner: string;
30+
name: string;
31+
description?: string;
32+
visibility: "public" | "private" | "internal";
33+
defaultBranch: string;
34+
hasIssues: boolean;
35+
hasWiki: boolean;
36+
hasActions: boolean;
37+
isArchived: boolean;
38+
};
39+
}
40+
41+
export default function RepoSettings({ repo }: RepoSettingsProps) {
42+
const [loading, setLoading] = useState(false);
43+
44+
async function handleUpdate(e: React.FormEvent<HTMLFormElement>) {
45+
e.preventDefault();
46+
setLoading(true);
47+
const formData = new FormData(e.currentTarget);
48+
const data = {
49+
name: formData.get("name"),
50+
description: formData.get("description"),
51+
defaultBranch: formData.get("defaultBranch"),
52+
hasIssues: formData.get("hasIssues") === "on",
53+
hasWiki: formData.get("hasWiki") === "on",
54+
hasActions: formData.get("hasActions") === "on",
55+
};
56+
57+
try {
58+
const res = await fetch(`/api/repos/${repo.owner}/${repo.name}`, {
59+
method: "PATCH",
60+
headers: { "Content-Type": "application/json" },
61+
body: JSON.stringify(data),
62+
});
63+
64+
if (!res.ok) throw new Error("Failed to update repository");
65+
66+
toast.success("Repository updated successfully");
67+
// Short delay to let the toast be seen before reload
68+
setTimeout(() => window.location.reload(), 500);
69+
} catch (err: any) {
70+
toast.error(err.message || "Failed to update repository");
71+
} finally {
72+
setLoading(false);
73+
}
74+
}
75+
76+
async function handleVisibility() {
77+
const newVisibility = repo.visibility === "public" ? "private" : "public";
78+
try {
79+
const res = await fetch(`/api/repos/${repo.owner}/${repo.name}`, {
80+
method: "PATCH",
81+
headers: { "Content-Type": "application/json" },
82+
body: JSON.stringify({ visibility: newVisibility }),
83+
});
84+
if (!res.ok) throw new Error("Failed to change visibility");
85+
86+
toast.success(`Visibility changed to ${newVisibility}`);
87+
setTimeout(() => window.location.reload(), 500);
88+
} catch (err: any) {
89+
toast.error(err.message || "Failed to change visibility");
90+
}
91+
}
92+
93+
async function handleArchive() {
94+
try {
95+
const res = await fetch(`/api/repos/${repo.owner}/${repo.name}`, {
96+
method: "PATCH",
97+
headers: { "Content-Type": "application/json" },
98+
body: JSON.stringify({ isArchived: !repo.isArchived }),
99+
});
100+
if (!res.ok) throw new Error("Failed to update archive status");
101+
102+
toast.success(`Repository ${repo.isArchived ? "unarchived" : "archived"}`);
103+
setTimeout(() => window.location.reload(), 500);
104+
} catch (err: any) {
105+
toast.error(err.message || "Failed to update archive status");
106+
}
107+
}
108+
109+
async function handleDelete() {
110+
try {
111+
const res = await fetch(`/api/repos/${repo.owner}/${repo.name}`, {
112+
method: "DELETE",
113+
});
114+
if (!res.ok) throw new Error("Failed to delete repository");
115+
116+
toast.success("Repository deleted");
117+
// Navigate immediately as the repo is gone
118+
window.location.href = "/dashboard";
119+
} catch (err: any) {
120+
toast.error(err.message || "Failed to delete repository");
121+
}
122+
}
123+
124+
return (
125+
<div className="space-y-8">
126+
<form onSubmit={handleUpdate}>
127+
<div className="space-y-6">
128+
<Card>
129+
<CardHeader>
130+
<CardTitle>General</CardTitle>
131+
<CardDescription>Update your repository details.</CardDescription>
132+
</CardHeader>
133+
<CardContent className="space-y-4">
134+
<div className="space-y-2">
135+
<Label htmlFor="name">Repository Name</Label>
136+
<Input
137+
id="name"
138+
name="name"
139+
defaultValue={repo.name}
140+
required
141+
/>
142+
</div>
143+
<div className="space-y-2">
144+
<Label htmlFor="description">Description</Label>
145+
<Textarea
146+
id="description"
147+
name="description"
148+
defaultValue={repo.description || ""}
149+
/>
150+
</div>
151+
<div className="space-y-2">
152+
<Label htmlFor="defaultBranch">Default Branch</Label>
153+
<Input
154+
id="defaultBranch"
155+
name="defaultBranch"
156+
defaultValue={repo.defaultBranch}
157+
/>
158+
</div>
159+
</CardContent>
160+
</Card>
161+
162+
<Card>
163+
<CardHeader>
164+
<CardTitle>Features</CardTitle>
165+
<CardDescription>
166+
Enable or disable repository features.
167+
</CardDescription>
168+
</CardHeader>
169+
<CardContent className="space-y-4">
170+
<div className="flex items-center space-x-2">
171+
<input
172+
type="checkbox"
173+
id="hasIssues"
174+
name="hasIssues"
175+
defaultChecked={repo.hasIssues}
176+
className="accent-primary h-4 w-4"
177+
/>
178+
<Label htmlFor="hasIssues">Issues</Label>
179+
</div>
180+
<div className="flex items-center space-x-2">
181+
<input
182+
type="checkbox"
183+
id="hasWiki"
184+
name="hasWiki"
185+
defaultChecked={repo.hasWiki}
186+
className="accent-primary h-4 w-4"
187+
/>
188+
<Label htmlFor="hasWiki">Wiki</Label>
189+
</div>
190+
<div className="flex items-center space-x-2">
191+
<input
192+
type="checkbox"
193+
id="hasActions"
194+
name="hasActions"
195+
defaultChecked={repo.hasActions}
196+
className="accent-primary h-4 w-4"
197+
/>
198+
<Label htmlFor="hasActions">Actions / Pipelines</Label>
199+
</div>
200+
</CardContent>
201+
<CardFooter className="border-t px-6 py-4">
202+
<Button type="submit" disabled={loading}>
203+
{loading ? "Saving..." : "Save changes"}
204+
</Button>
205+
</CardFooter>
206+
</Card>
207+
</div>
208+
</form>
209+
210+
<Card className="border-red-200">
211+
<CardHeader>
212+
<CardTitle className="text-red-600">Danger Zone</CardTitle>
213+
</CardHeader>
214+
<CardContent className="space-y-4">
215+
{/* Visibility */}
216+
<div className="flex items-center justify-between rounded-lg border border-red-200 p-4">
217+
<div>
218+
<h4 className="font-medium">Change visibility</h4>
219+
<p className="text-sm text-muted-foreground">
220+
This repository is currently {repo.visibility}.
221+
</p>
222+
</div>
223+
<AlertDialog>
224+
<AlertDialogTrigger asChild>
225+
<Button
226+
variant="outline"
227+
className="text-red-600 border-red-200 hover:bg-red-50"
228+
>
229+
Make {repo.visibility === "public" ? "private" : "public"}
230+
</Button>
231+
</AlertDialogTrigger>
232+
<AlertDialogContent>
233+
<AlertDialogHeader>
234+
<AlertDialogTitle>Change Visibility</AlertDialogTitle>
235+
<AlertDialogDescription>
236+
Are you sure you want to change visibility to{" "}
237+
{repo.visibility === "public" ? "private" : "public"}?
238+
</AlertDialogDescription>
239+
</AlertDialogHeader>
240+
<AlertDialogFooter>
241+
<AlertDialogCancel>Cancel</AlertDialogCancel>
242+
<AlertDialogAction onClick={handleVisibility} className="bg-red-600 hover:bg-red-700">
243+
Confirm
244+
</AlertDialogAction>
245+
</AlertDialogFooter>
246+
</AlertDialogContent>
247+
</AlertDialog>
248+
</div>
249+
250+
{/* Archive */}
251+
<div className="flex items-center justify-between rounded-lg border border-red-200 p-4">
252+
<div>
253+
<h4 className="font-medium">Archive repository</h4>
254+
<p className="text-sm text-muted-foreground">
255+
{repo.isArchived ? "Unarchive" : "Archive"} this repository.
256+
</p>
257+
</div>
258+
<AlertDialog>
259+
<AlertDialogTrigger asChild>
260+
<Button
261+
variant="outline"
262+
className="text-red-600 border-red-200 hover:bg-red-50"
263+
>
264+
{repo.isArchived ? "Unarchive" : "Archive"} repository
265+
</Button>
266+
</AlertDialogTrigger>
267+
<AlertDialogContent>
268+
<AlertDialogHeader>
269+
<AlertDialogTitle>{repo.isArchived ? "Unarchive" : "Archive"} Repository</AlertDialogTitle>
270+
<AlertDialogDescription>
271+
Are you sure you want to {repo.isArchived ? "unarchive" : "archive"} this repository?
272+
</AlertDialogDescription>
273+
</AlertDialogHeader>
274+
<AlertDialogFooter>
275+
<AlertDialogCancel>Cancel</AlertDialogCancel>
276+
<AlertDialogAction onClick={handleArchive} className="bg-red-600 hover:bg-red-700">
277+
Confirm
278+
</AlertDialogAction>
279+
</AlertDialogFooter>
280+
</AlertDialogContent>
281+
</AlertDialog>
282+
</div>
283+
284+
{/* Delete */}
285+
<div className="flex items-center justify-between rounded-lg border border-red-200 p-4">
286+
<div>
287+
<h4 className="font-medium">Delete this repository</h4>
288+
<p className="text-sm text-muted-foreground">
289+
Once you delete a repository, there is no going back. Please be
290+
certain.
291+
</p>
292+
</div>
293+
<AlertDialog>
294+
<AlertDialogTrigger asChild>
295+
<Button variant="destructive">Delete this repository</Button>
296+
</AlertDialogTrigger>
297+
<AlertDialogContent>
298+
<AlertDialogHeader>
299+
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
300+
<AlertDialogDescription>
301+
This action cannot be undone. This will permanently delete the
302+
repository <strong>{repo.owner}/{repo.name}</strong> and remove all contributor
303+
associations, issues, stars, and tags.
304+
</AlertDialogDescription>
305+
</AlertDialogHeader>
306+
<AlertDialogFooter>
307+
<AlertDialogCancel>Cancel</AlertDialogCancel>
308+
<AlertDialogAction onClick={handleDelete} className="bg-red-600 hover:bg-red-700">
309+
Delete
310+
</AlertDialogAction>
311+
</AlertDialogFooter>
312+
</AlertDialogContent>
313+
</AlertDialog>
314+
</div>
315+
</CardContent>
316+
</Card>
317+
</div>
318+
);
319+
}

0 commit comments

Comments
 (0)