Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ node_modules/
# We also don't want to ship these ignore rules to the users.
template/app/migrations
template/app/package-lock.json
.idea
4 changes: 4 additions & 0 deletions template/app/.env.server.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ AWS_S3_IAM_ACCESS_KEY=ACK...
AWS_S3_IAM_SECRET_KEY=t+33a...
AWS_S3_FILES_BUCKET=your-bucket-name
AWS_S3_REGION=your-region

# (OPTIONAL) Blog CMS — path to the blog Astro module for Markdown/sitemap sync on publish.
BLOG_MODULE_PATH=../blog
SITE_URL=https://your-site.com
6 changes: 5 additions & 1 deletion template/app/main.wasp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { App } from "./src/client/App" with { type: "ref" };
import { NotFoundPage } from "./src/client/components/NotFoundPage" with { type: "ref" };
import { serverEnvValidationSchema } from "./src/env" with { type: "ref" };
import { LandingPage } from "./src/landing-page/LandingPage" with { type: "ref" };
import { seedMockUsers } from "./src/server/scripts/dbSeeds" with { type: "ref" };
import { seedMockUsers, seedCmsData } from "./src/server/scripts/dbSeeds" with { type: "ref" };

import { adminSpec } from "./src/admin/admin.wasp";
import { analyticsSpec } from "./src/analytics/analytics.wasp";
Expand All @@ -15,6 +15,7 @@ import { fileUploadSpec } from "./src/file-upload/file-upload.wasp";
import { paymentSpec } from "./src/payment/payment.wasp";
import { emailSender } from "./src/server/emailSender.wasp";
import { userSpec } from "./src/user/user.wasp";
import { cmsSpec } from "./src/cms/cms.wasp";

export default app({
name: "OpenSaaS",
Expand All @@ -27,6 +28,8 @@ export default app({
seeds: [
// Populates the database with a bunch of fake users to work with during development.
seedMockUsers,
// Populates the database with sample CMS data (authors, tags, posts).
seedCmsData,
],
},
client: {
Expand All @@ -48,5 +51,6 @@ export default app({
fileUploadSpec,
analyticsSpec,
adminSpec,
cmsSpec,
],
});
60 changes: 58 additions & 2 deletions template/app/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
provider = "sqlite"
url = "file:./dev.db"
}

generator client {
Expand Down Expand Up @@ -109,3 +109,59 @@ model ContactFormMessage {
isRead Boolean @default(false)
repliedAt DateTime?
}

// ─── Blog CMS Models ──────────────────────────────────────────────

model Author {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
title String?
url String?
avatarUrl String?
posts PostAuthor[]
}

model Tag {
id String @id @default(uuid())
createdAt DateTime @default(now())
name String @unique
slug String @unique
posts PostTag[]
}

model Post {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
slug String @unique
subtitle String?
content String
status String @default("draft") // "draft" | "submitted" | "rejected" | "published"
publishedAt DateTime?
hideBannerImage Boolean @default(false)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
authors PostAuthor[]
tags PostTag[]
}

model PostAuthor {
postId String
authorId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
author Author @relation(fields: [authorId], references: [id], onDelete: Cascade)

@@id([postId, authorId])
}

model PostTag {
postId String
tagId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)

@@id([postId, tagId])
}
94 changes: 94 additions & 0 deletions template/app/src/admin/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import {
Calendar,
ChevronDown,
ChevronUp,
FileText,
LayoutDashboard,
LayoutTemplate,
Settings,
Sheet,
Tag,
Users,
X,
} from "lucide-react";
import React, { useEffect, useRef, useState } from "react";
Expand Down Expand Up @@ -145,6 +148,97 @@ export function Sidebar({ sidebarOpen, setSidebarOpen }: SidebarProps) {
</li>
{/* <!-- Menu Item Users --> */}

{/* <!-- Menu Item Blog CMS --> */}
<SidebarLinkGroup
activeCondition={
pathname.includes("cms")
}
>
{(handleClick, open) => {
return (
<React.Fragment>
<NavLink
to="#"
className={cn(
"text-muted-foreground hover:bg-accent hover:text-accent-foreground group relative flex items-center gap-2.5 rounded-sm px-4 py-2 font-medium duration-300 ease-in-out",
{
"bg-accent text-accent-foreground":
pathname.includes("cms"),
},
)}
onClick={(e) => {
e.preventDefault();
if (sidebarExpanded) {
handleClick();
} else {
setSidebarExpanded(true);
}
}}
>
<FileText />
博客 CMS
{open ? <ChevronUp /> : <ChevronDown />}
</NavLink>
{/* <!-- Dropdown Menu Start --> */}
<div
className={cn("translate transform overflow-hidden", {
hidden: !open,
})}
>
<ul className="mb-5.5 mt-4 flex flex-col gap-2.5 pl-6">
<li>
<NavLink
to={routes.AdminPostsRoute.to}
end
className={({ isActive }) =>
cn(
"text-muted-foreground hover:text-accent group relative flex items-center gap-2.5 rounded-md px-4 font-medium duration-300 ease-in-out",
{ "text-accent!": isActive },
)
}
>
文章管理
</NavLink>
</li>
<li>
<NavLink
to={routes.AdminAuthorsRoute.to}
end
className={({ isActive }) =>
cn(
"text-muted-foreground hover:text-accent group relative flex items-center gap-2.5 rounded-md px-4 font-medium duration-300 ease-in-out",
{ "text-accent!": isActive },
)
}
>
<Users className="h-4 w-4" />
作者管理
</NavLink>
</li>
<li>
<NavLink
to={routes.AdminTagsRoute.to}
end
className={({ isActive }) =>
cn(
"text-muted-foreground hover:text-accent group relative flex items-center gap-2.5 rounded-md px-4 font-medium duration-300 ease-in-out",
{ "text-accent!": isActive },
)
}
>
<Tag className="h-4 w-4" />
标签管理
</NavLink>
</li>
</ul>
</div>
{/* <!-- Dropdown Menu End --> */}
</React.Fragment>
);
}}
</SidebarLinkGroup>
{/* <!-- Menu Item Blog CMS --> */}

{/* <!-- Menu Item Settings --> */}
<li>
<NavLink
Expand Down
Loading