Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7dc9d6d
feat(auth): implement registration and identity endpoints (#3)
alikia2x Mar 9, 2026
7b3904f
refactor: reorganize repo into turbo monorepo (#6)
alikia2x Mar 9, 2026
adfd920
merge: develop into main - #9
alikia2x Mar 10, 2026
ad18386
refactor(auth): use Better Auth framework for auth (#8)
alikia2x Mar 11, 2026
671a236
feat(auth): set cookie for token on user creation (#12)
alikia2x Mar 11, 2026
b8d7f05
feat(song): implement song CRUD endpoints (#17)
alikia2x Mar 17, 2026
81b04bf
feat(auth): implement login/logout endpoints (#20)
alikia2x Mar 19, 2026
a44a4a8
refactor(backend): extract songHandler into a separate file (#27)
paywqiao-max Mar 21, 2026
2fbb509
chore: add `db:deploy` in scripts, update DATABASE_URL in example env…
alikia2x Mar 27, 2026
5418985
refactor: use more centralized error response schema (#30)
alikia2x Mar 28, 2026
2deedd8
feat: support MeiliSearch, refactor architecture (#32)
alikia2x Apr 1, 2026
fdace6b
feat(song): support creating song with lyrics (#38)
ji233-Sun Apr 4, 2026
99b8d49
refactor: observability, log & i18n support (#39)
alikia2x Apr 4, 2026
1d5bef6
test: improve test coverage for search (#42)
alikia2x Apr 11, 2026
1396eab
feat(song): add song lyrics CRUD endpoints (#46)
alikia2x Apr 12, 2026
0a714c2
feat(engine): add CRUD endpoints for SVS engine (#49)
alikia2x Apr 18, 2026
754117c
feat(artist): add CRUD endpoints for artists (#51)
alikia2x Apr 20, 2026
fff3b11
feat(artist): search service for artists (#53)
alikia2x Apr 26, 2026
97e72d2
feat(singer): add CRUD endpoints for singers (#54)
alikia2x Apr 29, 2026
6767191
docs(guide): update developer setup guide with redis and env notes
Aibeto May 16, 2026
1310a6f
docs: add og metadata and fix punctuation in docs
Aibeto May 16, 2026
ba6bb44
docs(guide): update env configuration instructions
Aibeto May 16, 2026
95ef7a6
docs: fix 404 page and update dev guide wording
Aibeto May 16, 2026
a4fc078
docs(website): add robots, manifest and sitemap for docs site
Aibeto May 16, 2026
f7a6df8
chore: commit message rule for TRAE (#56)
Aibeto May 16, 2026
3f745f7
feat(docs): add SEO optimization across landing and docs pages
Aibeto May 16, 2026
7eb098b
docs(git-commit): add git commit message specification document
Aibeto May 16, 2026
3a20d1a
Merge branch 'chore/aibeto-main-git_message' into update/aibeto-docs
Aibeto May 16, 2026
7b27604
Merge remote-tracking branch 'origin/update/aibeto-docs' into update/…
Aibeto May 16, 2026
1bca05c
docs(website): update landing page metadata and remove hardcoded title
Aibeto May 16, 2026
ffe158a
feat(artistRole): CRUD API for artistRole (#60)
alikia2x May 22, 2026
2d37c22
ci(docs): configure dynamic site URL and auto-generate sitemap
Aibeto Jun 8, 2026
cd22fb2
refactor(docs): refactor site metadata and robots configuration
Aibeto Jun 8, 2026
f9bb9c7
feat(llms-docs): add structured header and proper caching for LLM doc…
Aibeto Jun 15, 2026
30c687a
docs: clean up redundant og:title metadata, fix sitemap and OG image …
Aibeto Jun 15, 2026
c453a83
Merge remote-tracking branch 'origin/develop' into update/aibeto-26-S…
Aibeto Jun 15, 2026
432702c
refactor(metadata): unify 404 page metadata configuration and fix URL…
Aibeto Jun 15, 2026
79bd352
Revert "Merge remote-tracking branch 'origin/develop' into update/aib…
Aibeto Jun 15, 2026
f685c4f
refactor(docs): comply type rules for SEO/GEO files
Aibeto Jun 15, 2026
195e06d
chore: add missing files from previous commit 
Aibeto Jun 15, 2026
ac235b9
chore(ci/docs): CI config details and line endings tweaks
Aibeto Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ jobs:
deploy:
runs-on: ubuntu-latest
environment: production

permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -30,6 +31,8 @@ jobs:

- name: Build docs app
run: bun run build --filter=docs
env:
NEXT_PUBLIC_SITE_URL: https://docs.projectcvsa.com

- name: Verify build output
run: |
Expand All @@ -44,3 +47,4 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy apps/docs/out --project-name="cvsa-docs"

5 changes: 3 additions & 2 deletions .trae/rules/git-commit-message.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
alwaysApply: true
scene: git_message
language: en_US
---

### 基本格式
Expand All @@ -10,9 +11,9 @@ scene: git_message
```text
<类型>(<作用域>): <描述>

[正文]
[正文(英文描述)]

[脚注]
[脚注(英文描述)]
```

- **类型**:必填,表示本次提交的类别(见下文“类型列表”)。
Expand Down
38 changes: 33 additions & 5 deletions apps/docs/app/(docs)/[lang]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { getMDXComponents } from "@/components/mdx";
import type { Metadata } from "next";
import { createRelativeLink } from "fumadocs-ui/mdx";
import { gitConfig } from "@/lib/layout.shared";
import { MarkdownCopyButton, ViewOptionsPopover } from "@/components/page-actions";
import { MarkdownCopyButton, ViewOptionsPopover } from "@/components/PageActions";
import { buttonVariants } from "fumadocs-ui/components/ui/button";
import { t } from "@/lib/i18n";
import { siteUrl } from "@/lib/env";
import { ogImageUrl, OG_IMAGE_SIZE } from "@/lib/metadata";

export async function generateStaticParams() {
const pages = source.getPages();
Expand Down Expand Up @@ -79,19 +81,45 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { slug, lang } = await params;
const page = source.getPage(slug, lang);
const baseUrl = "https://docs.projectcvsa.com";
if (!page) notFound();
const cleanSlug = slug ? slug : [];
const currentPage = cleanSlug.join("/");
const pagePath = currentPage ? `/${currentPage}` : "";

const url = `${siteUrl}/${lang}${pagePath}`;
const ogImage = ogImageUrl();
return {
title: page.data.title,
description: page.data.description,
alternates: {
canonical: url,
languages: {
zh: `${baseUrl}/zh/${currentPage}`,
en: `${baseUrl}/en/${currentPage}`,
"x-default": `${baseUrl}/en/${currentPage}`,
zh: `${siteUrl}/zh${pagePath}`,
en: `${siteUrl}/en${pagePath}`,
"x-default": `${siteUrl}/en${pagePath}`,
},
},
openGraph: {
title: page.data.title,
description: page.data.description,
url,
siteName: t("siteName", lang),
locale: lang === "zh" ? "zh_CN" : "en_US",
type: "article",
images: [
{
url: ogImage,
width: OG_IMAGE_SIZE,
height: OG_IMAGE_SIZE,
alt: page.data.title,
},
],
},
// twitter: {
// card: "summary_large_image",
// title: page.data.title,
// description: page.data.description,
// images: [ogImage],
// },
};
}
42 changes: 40 additions & 2 deletions apps/docs/app/(landing)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
import "./global.css";
import { Funnel_Sans } from "next/font/google";
import { UpdateLanguage } from "@/components/update-language";
import { UpdateLanguage } from "@/components/UpdateLanguage";
import type { Metadata } from "next";
import { siteUrl } from "@/lib/env";
import { t } from "@/lib/i18n";
import { SITE_TITLE, SITE_DESCRIPTION, ogImageUrl, OG_IMAGE_SIZE } from "@/lib/metadata";

const inter = Funnel_Sans({
subsets: ["latin"],
});

export const metadata: Metadata = {
title: SITE_TITLE,
description: SITE_DESCRIPTION,
alternates: {
canonical: siteUrl,
languages: {
en: `${siteUrl}/en`,
zh: `${siteUrl}/zh`,
"x-default": `${siteUrl}/en`,
},
},
openGraph: {
title: SITE_TITLE,
description: SITE_DESCRIPTION,
url: siteUrl,
siteName: t("siteName", "en"),
locale: "en_US",
type: "website",
images: [
{
url: ogImageUrl(),
width: OG_IMAGE_SIZE,
height: OG_IMAGE_SIZE,
alt: SITE_TITLE,
},
],
},
// twitter: {
// card: "summary_large_image",
// title: SITE_TITLE,
// description: SITE_DESCRIPTION,
// images: [ogImageUrl()],
// },
};

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.className} suppressHydrationWarning>
<title>Project CVSA | Archive for a Better Future</title>
<body className="relative min-w-screen min-h-screen flex flex-col justify-center overflow-hidden dark:bg-black">
<UpdateLanguage />
{children}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/app/(landing)/old/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HeroLinks } from "@/components/links";
import { HeroLinks } from "@/components/Links";
import { LogoEnglish } from "@/components/icon/LogoEnglish";

export default function Page() {
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/app/(landing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { HeroLinks } from "@/components/links";
import { HeroLinks } from "@/components/Links";
import { LogoEnglish } from "@/components/icon/LogoEnglish";
import { DigitGrid } from "./DigitGrid";
import { Description } from "./Description";
Expand Down
65 changes: 62 additions & 3 deletions apps/docs/app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,69 @@
import { getLLMText, source } from "@/lib/source";
import { siteUrl } from "@/lib/env";
import { t } from "@/lib/i18n";

export const revalidate = false;

const HEADER = `# Project CVSA | 中V档案馆 — Full Documentation

> Project CVSA (中V档案馆) is an archive program that collects and preserves
> information about the Chinese singing voice synthesis community. This file
> contains the full markdown content of every documentation page on this site,
> concatenated for easy ingestion by LLMs and other automated tools.

## How to use this file

- The content is grouped by language, then by page.
- Each page is preceded by a level-2 heading with its title and URL.
- Pages within a language are separated by \`---\` dividers.
- Prefer citing the canonical page URL (linked in each section heading) over
copying text from this file, so links stay current.

## Project links

- Project home: ${siteUrl}
- English docs: ${siteUrl}/en
- 中文文档: ${siteUrl}/zh
- Index of all pages: ${siteUrl}/llms.txt
- Markdown source of any single page: append \`/raw/<lang>/<page>.mdx\` to the site URL
- Sitemap: ${siteUrl}/sitemap.xml

---

`;

const SECTION_DIVIDER = "\n\n---\n\n";

export async function GET() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);
const pages = source.getPages();
const sections: string[] = [];

for (const lang of ["en", "zh"] as const) {
const langPages = pages.filter((p) => p.locale === lang);
if (langPages.length === 0) continue;

const contents = await Promise.all(langPages.map(getLLMText));

const blocks = langPages.map((page, i) => {
const slug = page.slugs.join("/");
const url = slug
? `${siteUrl}/${lang}/${slug}`
: `${siteUrl}/${lang}`;
const title = page.data.title ?? slug;
return `## [${title}](${url})\n\n${contents[i]}`;
});

sections.push(
`# ${t("siteName", lang)} — ${lang.toUpperCase()}\n\n${blocks.join(SECTION_DIVIDER)}`,
);
}

const body = `${HEADER}${sections.join(SECTION_DIVIDER)}`;

return new Response(scanned.join("\n\n"));
return new Response(body, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
}
35 changes: 34 additions & 1 deletion apps/docs/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
import { source } from "@/lib/source";
import { llms } from "fumadocs-core/source";
import { siteUrl } from "@/lib/env";

export const revalidate = false;

const HEADER = `# Project CVSA | 中V档案馆

> Project CVSA (中V档案馆) is an archive program that collects and preserves
> information about the Chinese singing voice synthesis community. This site
> documents the project's mission, data model, architecture, and contribution
> guidelines for both English and Chinese readers.

This file is an LLM-friendly index of every documentation page on this site.
Each entry links to a page and summarizes what it covers.

- Project home: ${siteUrl}
- English docs: ${siteUrl}/en
- 中文文档: ${siteUrl}/zh
- Full content of all pages: ${siteUrl}/llms-full.txt
- Markdown source of any page: append \`/raw/<lang>/<page>.mdx\` to the site URL
- Sitemap: ${siteUrl}/sitemap.xml

For each page, prefer the canonical URL that matches the reader's language.
When in doubt, link to the English version.

---

`;

export function GET() {
return new Response(llms(source).index());
const index = llms(source).index();
const body = `${HEADER}${index}`;

return new Response(body, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
}
39 changes: 36 additions & 3 deletions apps/docs/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
import "./global.css";
import { Inter } from "next/font/google";
import { UpdateLanguage } from "@/components/update-language";
import { UpdateLanguage } from "@/components/UpdateLanguage";
import type { Metadata } from "next";
import { SITE_TITLE_404, SITE_DESCRIPTION_404, ogImageUrl, OG_IMAGE_SIZE } from "@/lib/metadata";

const inter = Inter({
subsets: ["latin"],
});

export const metadata: Metadata = {
title: `${SITE_TITLE_404}`,
description: SITE_DESCRIPTION_404,
robots: { index: false, follow: false },
openGraph: {
title: `${SITE_TITLE_404}`,
description: SITE_DESCRIPTION_404,
images: [
{
url: ogImageUrl(),
width: OG_IMAGE_SIZE,
height: OG_IMAGE_SIZE,
alt: SITE_TITLE_404,
},
],
},
// twitter: {
// card: "summary_large_image",
// title: `Not Found - ${SITE_TITLE_404}`,
// description: SITE_DESCRIPTION_404,
// images: [ogImageUrl()],
// },
};

export default function NotFound() {
return (
<html lang="en" className={inter.className}>
<body className="flex flex-col min-h-screen">
<body className="flex flex-col min-h-screen items-center justify-center gap-4">
<UpdateLanguage />
<h1>Not Found</h1>
<h1 className="text-4xl font-bold">404</h1>
<p className="text-fd-muted-foreground">Page not found</p>
<a
href="/en"
className="text-fd-primary underline underline-offset-4 hover:text-fd-primary/80"
>
Go to homepage
</a>
</body>
</html>
);
Expand Down
12 changes: 12 additions & 0 deletions apps/docs/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { MetadataRoute } from "next";
import { siteUrl } from "@/lib/env";

export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: `${siteUrl}/sitemap.xml`,
};
}
28 changes: 28 additions & 0 deletions apps/docs/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { MetadataRoute } from "next";
import { source } from "@/lib/source";
import { siteUrl } from "@/lib/env";

export default function sitemap(): MetadataRoute.Sitemap {
const pages = source.getPages();

const docPages = pages.map((page) => {
const slug = page.slugs.join("/");
const url = `${siteUrl}/${page.locale}${slug ? `/${slug}` : ""}`;

return {
url,
// lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: page.slugs.length === 0 ? 0.8 : 0.6,
};
});

const landingPage: MetadataRoute.Sitemap[number] = {
url: siteUrl,
// lastModified: new Date(),
changeFrequency: "monthly",
priority: 1.0,
};

return [landingPage, ...docPages];
}
File renamed without changes.
Loading