mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-09 04:56:45 +03:00
Add fork tags to repository UI and enhance organization cards with repository breakdown
- Add fork tags to repository table and dashboard list components - Display 'Fork' badge for repositories where isForked is true - Enhance organization cards to show breakdown of public, private, and fork repositories - Update organization API to respect user configuration filters (private repos, forks) - Add visual indicators with colored dots for each repository type - Ensure consistent filtering between repository and organization APIs - Fix issue where private repositories weren't showing due to configuration filtering
This commit is contained in:
@@ -81,6 +81,11 @@ export function RepositoryList({ repositories }: RepositoryListProps) {
|
|||||||
Private
|
Private
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{repo.isForked && (
|
||||||
|
<span className="rounded-full bg-muted px-2 py-0.5 text-xs">
|
||||||
|
Fork
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -118,10 +118,38 @@ export function OrganizationList({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<div className="text-sm text-muted-foreground mb-4">
|
||||||
{org.repositoryCount}{" "}
|
<div className="flex items-center justify-between">
|
||||||
{org.repositoryCount === 1 ? "repository" : "repositories"}
|
<span className="font-medium">
|
||||||
</p>
|
{org.repositoryCount}{" "}
|
||||||
|
{org.repositoryCount === 1 ? "repository" : "repositories"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{(org.publicRepositoryCount !== undefined ||
|
||||||
|
org.privateRepositoryCount !== undefined ||
|
||||||
|
org.forkRepositoryCount !== undefined) && (
|
||||||
|
<div className="flex gap-4 mt-2 text-xs">
|
||||||
|
{org.publicRepositoryCount !== undefined && (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<div className="h-2 w-2 rounded-full bg-green-500" />
|
||||||
|
{org.publicRepositoryCount} public
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{org.privateRepositoryCount !== undefined && org.privateRepositoryCount > 0 && (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<div className="h-2 w-2 rounded-full bg-orange-500" />
|
||||||
|
{org.privateRepositoryCount} private
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{org.forkRepositoryCount !== undefined && org.forkRepositoryCount > 0 && (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<div className="h-2 w-2 rounded-full bg-blue-500" />
|
||||||
|
{org.forkRepositoryCount} fork{org.forkRepositoryCount !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ export default function RepositoryTable({
|
|||||||
Private
|
Private
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{repo.isForked && (
|
||||||
|
<span className="ml-2 rounded-full bg-muted px-2 py-0.5 text-xs">
|
||||||
|
Fork
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Owner */}
|
{/* Owner */}
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ export const organizationSchema = z.object({
|
|||||||
errorMessage: z.string().optional(),
|
errorMessage: z.string().optional(),
|
||||||
|
|
||||||
repositoryCount: z.number().default(0),
|
repositoryCount: z.number().default(0),
|
||||||
|
publicRepositoryCount: z.number().optional(),
|
||||||
|
privateRepositoryCount: z.number().optional(),
|
||||||
|
forkRepositoryCount: z.number().optional(),
|
||||||
|
|
||||||
createdAt: z.date().default(() => new Date()),
|
createdAt: z.date().default(() => new Date()),
|
||||||
updatedAt: z.date().default(() => new Date()),
|
updatedAt: z.date().default(() => new Date()),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { organizations } from "@/lib/db";
|
import { organizations, repositories, configs } from "@/lib/db";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql, and, count } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
membershipRoleEnum,
|
membershipRoleEnum,
|
||||||
type OrganizationsApiResponse,
|
type OrganizationsApiResponse,
|
||||||
@@ -25,24 +25,114 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Fetch the user's active configuration to respect filtering settings
|
||||||
|
const [config] = await db
|
||||||
|
.select()
|
||||||
|
.from(configs)
|
||||||
|
.where(and(eq(configs.userId, userId), eq(configs.isActive, true)));
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return jsonResponse({
|
||||||
|
data: {
|
||||||
|
success: false,
|
||||||
|
error: "No active configuration found for this user",
|
||||||
|
},
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const githubConfig = config.githubConfig as {
|
||||||
|
mirrorStarred: boolean;
|
||||||
|
skipForks: boolean;
|
||||||
|
privateRepositories: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const rawOrgs = await db
|
const rawOrgs = await db
|
||||||
.select()
|
.select()
|
||||||
.from(organizations)
|
.from(organizations)
|
||||||
.where(eq(organizations.userId, userId))
|
.where(eq(organizations.userId, userId))
|
||||||
.orderBy(sql`name COLLATE NOCASE`);
|
.orderBy(sql`name COLLATE NOCASE`);
|
||||||
|
|
||||||
const orgsWithIds: Organization[] = rawOrgs.map((org) => ({
|
// Calculate repository breakdowns for each organization
|
||||||
...org,
|
const orgsWithBreakdown = await Promise.all(
|
||||||
status: repoStatusEnum.parse(org.status),
|
rawOrgs.map(async (org) => {
|
||||||
membershipRole: membershipRoleEnum.parse(org.membershipRole),
|
// Build base conditions for this organization (without private/fork filters)
|
||||||
lastMirrored: org.lastMirrored ?? undefined,
|
const baseConditions = [
|
||||||
errorMessage: org.errorMessage ?? undefined,
|
eq(repositories.userId, userId),
|
||||||
}));
|
eq(repositories.organization, org.name)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!githubConfig.mirrorStarred) {
|
||||||
|
baseConditions.push(eq(repositories.isStarred, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total count with all user config filters applied
|
||||||
|
const totalConditions = [...baseConditions];
|
||||||
|
if (githubConfig.skipForks) {
|
||||||
|
totalConditions.push(eq(repositories.isForked, false));
|
||||||
|
}
|
||||||
|
if (!githubConfig.privateRepositories) {
|
||||||
|
totalConditions.push(eq(repositories.isPrivate, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
const [totalCount] = await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(repositories)
|
||||||
|
.where(and(...totalConditions));
|
||||||
|
|
||||||
|
// Get public count
|
||||||
|
const publicConditions = [...baseConditions, eq(repositories.isPrivate, false)];
|
||||||
|
if (githubConfig.skipForks) {
|
||||||
|
publicConditions.push(eq(repositories.isForked, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
const [publicCount] = await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(repositories)
|
||||||
|
.where(and(...publicConditions));
|
||||||
|
|
||||||
|
// Get private count (only if private repos are enabled in config)
|
||||||
|
const [privateCount] = githubConfig.privateRepositories ? await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(repositories)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
...baseConditions,
|
||||||
|
eq(repositories.isPrivate, true),
|
||||||
|
...(githubConfig.skipForks ? [eq(repositories.isForked, false)] : [])
|
||||||
|
)
|
||||||
|
) : [{ count: 0 }];
|
||||||
|
|
||||||
|
// Get fork count (only if forks are enabled in config)
|
||||||
|
const [forkCount] = !githubConfig.skipForks ? await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(repositories)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
...baseConditions,
|
||||||
|
eq(repositories.isForked, true),
|
||||||
|
...(!githubConfig.privateRepositories ? [eq(repositories.isPrivate, false)] : [])
|
||||||
|
)
|
||||||
|
) : [{ count: 0 }];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...org,
|
||||||
|
status: repoStatusEnum.parse(org.status),
|
||||||
|
membershipRole: membershipRoleEnum.parse(org.membershipRole),
|
||||||
|
lastMirrored: org.lastMirrored ?? undefined,
|
||||||
|
errorMessage: org.errorMessage ?? undefined,
|
||||||
|
repositoryCount: totalCount.count,
|
||||||
|
publicRepositoryCount: publicCount.count,
|
||||||
|
privateRepositoryCount: privateCount.count,
|
||||||
|
forkRepositoryCount: forkCount.count,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const resPayload: OrganizationsApiResponse = {
|
const resPayload: OrganizationsApiResponse = {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Organizations fetched successfully",
|
message: "Organizations fetched successfully",
|
||||||
organizations: orgsWithIds,
|
organizations: orgsWithBreakdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
return jsonResponse({ data: resPayload, status: 200 });
|
return jsonResponse({ data: resPayload, status: 200 });
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export interface GitOrg {
|
|||||||
isIncluded: boolean;
|
isIncluded: boolean;
|
||||||
status: RepoStatus;
|
status: RepoStatus;
|
||||||
repositoryCount: number;
|
repositoryCount: number;
|
||||||
|
publicRepositoryCount?: number;
|
||||||
|
privateRepositoryCount?: number;
|
||||||
|
forkRepositoryCount?: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user