Initial Montech monorepo: Nuxt landing, Vue admin, Nest API.
Scaffold SSR site with CMS/blog/gallery/SEO admin and deploy configs for montech.ir. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { BlogService } from "./blog.service";
|
||||
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
|
||||
import { CreateBlogDto, UpdateBlogDto } from "./blog.dto";
|
||||
|
||||
@Controller()
|
||||
export class BlogController {
|
||||
constructor(private blog: BlogService) {}
|
||||
|
||||
@Get("public/blog")
|
||||
listPublic() {
|
||||
return this.blog.listPublic();
|
||||
}
|
||||
|
||||
@Get("public/blog/:slug")
|
||||
getPublic(@Param("slug") slug: string) {
|
||||
return this.blog.getBySlug(slug);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get("admin/blog")
|
||||
listAdmin() {
|
||||
return this.blog.listAdmin();
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get("admin/blog/:id")
|
||||
getAdmin(@Param("id") id: string) {
|
||||
return this.blog.getAdmin(id);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post("admin/blog")
|
||||
create(@Body() dto: CreateBlogDto) {
|
||||
return this.blog.create(dto);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put("admin/blog/:id")
|
||||
update(@Param("id") id: string, @Body() dto: UpdateBlogDto) {
|
||||
return this.blog.update(id, dto);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete("admin/blog/:id")
|
||||
remove(@Param("id") id: string) {
|
||||
return this.blog.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MinLength,
|
||||
} from "class-validator";
|
||||
|
||||
export class CreateBlogDto {
|
||||
@IsString() @MinLength(1) slug!: string;
|
||||
@IsString() @MinLength(1) title!: string;
|
||||
@IsOptional() @IsString() excerpt?: string;
|
||||
@IsOptional() @IsString() content?: string;
|
||||
@IsOptional() @IsString() coverUrl?: string;
|
||||
@IsOptional() @IsBoolean() published?: boolean;
|
||||
@IsOptional() @IsString() seoTitle?: string;
|
||||
@IsOptional() @IsString() seoDesc?: string;
|
||||
@IsOptional() @IsString() ogImage?: string;
|
||||
@IsOptional() @IsString() canonical?: string;
|
||||
}
|
||||
|
||||
export class UpdateBlogDto {
|
||||
@IsOptional() @IsString() @MinLength(1) slug?: string;
|
||||
@IsOptional() @IsString() @MinLength(1) title?: string;
|
||||
@IsOptional() @IsString() excerpt?: string;
|
||||
@IsOptional() @IsString() content?: string;
|
||||
@IsOptional() @IsString() coverUrl?: string;
|
||||
@IsOptional() @IsBoolean() published?: boolean;
|
||||
@IsOptional() @IsString() seoTitle?: string;
|
||||
@IsOptional() @IsString() seoDesc?: string;
|
||||
@IsOptional() @IsString() ogImage?: string;
|
||||
@IsOptional() @IsString() canonical?: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { BlogService } from "./blog.service";
|
||||
import { BlogController } from "./blog.controller";
|
||||
|
||||
@Module({
|
||||
controllers: [BlogController],
|
||||
providers: [BlogService],
|
||||
})
|
||||
export class BlogModule {}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class BlogService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
listPublic() {
|
||||
return this.prisma.blogPost.findMany({
|
||||
where: { published: true },
|
||||
orderBy: { publishedAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
excerpt: true,
|
||||
coverUrl: true,
|
||||
publishedAt: true,
|
||||
seoTitle: true,
|
||||
seoDesc: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getBySlug(slug: string) {
|
||||
const post = await this.prisma.blogPost.findFirst({
|
||||
where: { slug, published: true },
|
||||
});
|
||||
if (!post) throw new NotFoundException();
|
||||
return post;
|
||||
}
|
||||
|
||||
listAdmin() {
|
||||
return this.prisma.blogPost.findMany({ orderBy: { updatedAt: "desc" } });
|
||||
}
|
||||
|
||||
getAdmin(id: string) {
|
||||
return this.prisma.blogPost.findUniqueOrThrow({ where: { id } });
|
||||
}
|
||||
|
||||
create(data: {
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt?: string;
|
||||
content?: string;
|
||||
coverUrl?: string;
|
||||
published?: boolean;
|
||||
seoTitle?: string;
|
||||
seoDesc?: string;
|
||||
ogImage?: string;
|
||||
canonical?: string;
|
||||
}) {
|
||||
return this.prisma.blogPost.create({
|
||||
data: {
|
||||
...data,
|
||||
publishedAt: data.published ? new Date() : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: {
|
||||
slug?: string;
|
||||
title?: string;
|
||||
excerpt?: string;
|
||||
content?: string;
|
||||
coverUrl?: string | null;
|
||||
published?: boolean;
|
||||
seoTitle?: string | null;
|
||||
seoDesc?: string | null;
|
||||
ogImage?: string | null;
|
||||
canonical?: string | null;
|
||||
},
|
||||
) {
|
||||
const existing = await this.prisma.blogPost.findUniqueOrThrow({ where: { id } });
|
||||
const publishedAt =
|
||||
data.published === true && !existing.publishedAt
|
||||
? new Date()
|
||||
: data.published === false
|
||||
? null
|
||||
: existing.publishedAt;
|
||||
return this.prisma.blogPost.update({
|
||||
where: { id },
|
||||
data: { ...data, publishedAt },
|
||||
});
|
||||
}
|
||||
|
||||
remove(id: string) {
|
||||
return this.prisma.blogPost.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user