This commit is contained in:
ditiqi 2025-01-26 16:10:31 +08:00
parent 1266d076b1
commit ed4b328047
17 changed files with 204 additions and 116 deletions

View File

@ -4,16 +4,19 @@ import { AppConfigService } from './app-config.service';
import { z, ZodType } from 'zod'; import { z, ZodType } from 'zod';
import { Prisma } from '@nice/common'; import { Prisma } from '@nice/common';
import { RealtimeServer } from '@server/socket/realtime/realtime.server'; import { RealtimeServer } from '@server/socket/realtime/realtime.server';
const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> = z.any() const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> =
const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any() z.any();
const AppConfigDeleteManyArgsSchema: ZodType<Prisma.AppConfigDeleteManyArgs> = z.any() const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any();
const AppConfigFindFirstArgsSchema: ZodType<Prisma.AppConfigFindFirstArgs> = z.any() const AppConfigDeleteManyArgsSchema: ZodType<Prisma.AppConfigDeleteManyArgs> =
z.any();
const AppConfigFindFirstArgsSchema: ZodType<Prisma.AppConfigFindFirstArgs> =
z.any();
@Injectable() @Injectable()
export class AppConfigRouter { export class AppConfigRouter {
constructor( constructor(
private readonly trpc: TrpcService, private readonly trpc: TrpcService,
private readonly appConfigService: AppConfigService, private readonly appConfigService: AppConfigService,
private readonly realtimeServer: RealtimeServer private readonly realtimeServer: RealtimeServer,
) {} ) {}
router = this.trpc.router({ router = this.trpc.router({
create: this.trpc.protectProcedure create: this.trpc.protectProcedure
@ -25,23 +28,24 @@ export class AppConfigRouter {
update: this.trpc.protectProcedure update: this.trpc.protectProcedure
.input(AppConfigUpdateArgsSchema) .input(AppConfigUpdateArgsSchema)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const { staff } = ctx; const { staff } = ctx;
return await this.appConfigService.update(input); return await this.appConfigService.update(input);
}), }),
deleteMany: this.trpc.protectProcedure.input(AppConfigDeleteManyArgsSchema).mutation(async ({ input }) => { deleteMany: this.trpc.protectProcedure
return await this.appConfigService.deleteMany(input) .input(AppConfigDeleteManyArgsSchema)
.mutation(async ({ input }) => {
return await this.appConfigService.deleteMany(input);
}), }),
findFirst: this.trpc.protectProcedure.input(AppConfigFindFirstArgsSchema). findFirst: this.trpc.protectProcedure
query(async ({ input }) => { .input(AppConfigFindFirstArgsSchema)
.query(async ({ input }) => {
return await this.appConfigService.findFirst(input) return await this.appConfigService.findFirst(input);
}), }),
clearRowCache: this.trpc.protectProcedure.mutation(async () => { clearRowCache: this.trpc.protectProcedure.mutation(async () => {
return await this.appConfigService.clearRowCache() return await this.appConfigService.clearRowCache();
}), }),
getClientCount: this.trpc.protectProcedure.query(() => { getClientCount: this.trpc.protectProcedure.query(() => {
return this.realtimeServer.getClientCount() return this.realtimeServer.getClientCount();
}) }),
}); });
} }

View File

@ -1,10 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import { db, ObjectType, Prisma } from '@nice/common';
db,
ObjectType,
Prisma,
} from '@nice/common';
import { BaseService } from '../base/base.service'; import { BaseService } from '../base/base.service';
import { deleteByPattern } from '@server/utils/redis/utils'; import { deleteByPattern } from '@server/utils/redis/utils';
@ -12,10 +7,10 @@ import { deleteByPattern } from '@server/utils/redis/utils';
@Injectable() @Injectable()
export class AppConfigService extends BaseService<Prisma.AppConfigDelegate> { export class AppConfigService extends BaseService<Prisma.AppConfigDelegate> {
constructor() { constructor() {
super(db, "appConfig"); super(db, 'appConfig');
} }
async clearRowCache() { async clearRowCache() {
await deleteByPattern("row-*") await deleteByPattern('row-*');
return true return true;
} }
} }

View File

@ -187,6 +187,13 @@ export class VisitService extends BaseService<Prisma.VisitDelegate> {
visitType: VisitType.LIKE, visitType: VisitType.LIKE,
}); });
} }
if (args.where.type === VisitType.HATE) {
EventBus.emit('updateVisitCount', {
objectType: ObjectType.POST,
id: args?.where?.postId as string,
visitType: VisitType.HATE,
});
}
} }
return superDetele; return superDetele;
} }

View File

@ -1,5 +1,6 @@
import { db, PostState, PostType, VisitType } from '@nice/common'; import { db, PostState, PostType, VisitType } from '@nice/common';
export async function updatePostViewCount(id: string, type: VisitType) { export async function updatePostViewCount(id: string, type: VisitType) {
console.log('updatePostViewCount', type);
const totalViews = await db.visit.aggregate({ const totalViews = await db.visit.aggregate({
_sum: { _sum: {
views: true, views: true,
@ -19,7 +20,6 @@ export async function updatePostViewCount(id: string, type: VisitType) {
}, },
}); });
} else if (type === VisitType.LIKE) { } else if (type === VisitType.LIKE) {
console.log('totalViews._sum.view', totalViews._sum.views);
await db.post.update({ await db.post.update({
where: { where: {
id: id, id: id,
@ -28,5 +28,14 @@ export async function updatePostViewCount(id: string, type: VisitType) {
likes: totalViews._sum.views || 0, // Use 0 if no visits exist likes: totalViews._sum.views || 0, // Use 0 if no visits exist
}, },
}); });
} else if (type === VisitType.HATE) {
await db.post.update({
where: {
id: id,
},
data: {
hates: totalViews._sum.views || 0, // Use 0 if no visits exist
},
});
} }
} }

View File

@ -8,7 +8,6 @@ import { updatePostViewCount } from '../models/post/utils';
const logger = new Logger('QueueWorker'); const logger = new Logger('QueueWorker');
export default async function processJob(job: Job<any, any, QueueJobType>) { export default async function processJob(job: Job<any, any, QueueJobType>) {
try { try {
if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) { if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) {
await updatePostViewCount(job.data.id, job.data.type); await updatePostViewCount(job.data.id, job.data.type);
} }

View File

@ -12,12 +12,7 @@ import {
Term, Term,
} from '@nice/common'; } from '@nice/common';
import EventBus from '@server/utils/event-bus'; import EventBus from '@server/utils/event-bus';
import { import { capitalizeFirstLetter, DevDataCounts, getCounts } from './utils';
capitalizeFirstLetter,
DevDataCounts,
getCounts,
} from './utils';
import { StaffService } from '@server/models/staff/staff.service'; import { StaffService } from '@server/models/staff/staff.service';
@Injectable() @Injectable()
export class GenDevService { export class GenDevService {
@ -26,7 +21,7 @@ export class GenDevService {
deptStaffRecord: Record<string, Staff[]> = {}; deptStaffRecord: Record<string, Staff[]> = {};
terms: Record<TaxonomySlug, Term[]> = { terms: Record<TaxonomySlug, Term[]> = {
[TaxonomySlug.CATEGORY]: [], [TaxonomySlug.CATEGORY]: [],
[TaxonomySlug.TAG]: [] [TaxonomySlug.TAG]: [],
}; };
depts: Department[] = []; depts: Department[] = [];
domains: Department[] = []; domains: Department[] = [];
@ -47,7 +42,6 @@ export class GenDevService {
await this.generateDepartments(3, 6); await this.generateDepartments(3, 6);
await this.generateTerms(1, 3); await this.generateTerms(1, 3);
await this.generateStaffs(4); await this.generateStaffs(4);
} catch (err) { } catch (err) {
this.logger.error(err); this.logger.error(err);
} }
@ -164,8 +158,8 @@ export class GenDevService {
showname: username, showname: username,
username: username, username: username,
deptId: dept.id, deptId: dept.id,
domainId: domain.id domainId: domain.id,
} },
}); });
// Update both deptStaffRecord and staffs array // Update both deptStaffRecord and staffs array
this.deptStaffRecord[dept.id].push(staff); this.deptStaffRecord[dept.id].push(staff);
@ -190,7 +184,7 @@ export class GenDevService {
name, name,
isDomain: currentDepth === 1 ? true : false, isDomain: currentDepth === 1 ? true : false,
parentId, parentId,
} },
}); });
return department; return department;
} }
@ -208,7 +202,9 @@ export class GenDevService {
throw new Error(`Taxonomy with slug ${taxonomySlug} not found`); throw new Error(`Taxonomy with slug ${taxonomySlug} not found`);
} }
this.logger.log(`Creating terms for taxonomy: ${taxonomy.name} (${taxonomy.slug})`); this.logger.log(
`Creating terms for taxonomy: ${taxonomy.name} (${taxonomy.slug})`,
);
let counter = 1; let counter = 1;
const createTermTree = async ( const createTermTree = async (
parentId: string | null, parentId: string | null,
@ -223,7 +219,7 @@ export class GenDevService {
taxonomyId: taxonomy!.id, taxonomyId: taxonomy!.id,
domainId: domain?.id, domainId: domain?.id,
parentId, parentId,
} },
}); });
this.terms[taxonomySlug].push(newTerm); this.terms[taxonomySlug].push(newTerm);
await createTermTree(newTerm.id, currentDepth + 1); await createTermTree(newTerm.id, currentDepth + 1);

View File

@ -1,32 +1,25 @@
import { import { AppConfigSlug, BaseSetting, RolePerms } from "@nice/common";
AppConfigSlug,
BaseSetting,
RolePerms,
} from "@nice/common";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { import { Button, Form, Input, message, theme } from "antd";
Button,
Form,
Input,
message,
theme,
} from "antd";
import { useAppConfig } from "@nice/client"; import { useAppConfig } from "@nice/client";
import { useAuth } from "@web/src/providers/auth-provider"; import { useAuth } from "@web/src/providers/auth-provider";
import { useForm } from "antd/es/form/Form"; import { useForm } from "antd/es/form/Form";
import { api } from "@nice/client" import { api } from "@nice/client";
import AdminHeader from "@web/src/components/layout/admin/AdminHeader"; import AdminHeader from "@web/src/components/layout/admin/AdminHeader";
import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
export default function BaseSettingPage() { export default function BaseSettingPage() {
const { update, baseSetting } = useAppConfig(); const { update, baseSetting } = useAppConfig();
const utils = api.useUtils() const utils = api.useUtils();
const [form] = useForm() const [form] = useForm();
const { token } = theme.useToken(); const { token } = theme.useToken();
const { data: clientCount } = api.app_config.getClientCount.useQuery(undefined, { const { data: clientCount } = api.app_config.getClientCount.useQuery(
undefined,
{
refetchInterval: 3000, refetchInterval: 3000,
refetchIntervalInBackground: true refetchIntervalInBackground: true,
}) }
);
const [isFormChanged, setIsFormChanged] = useState(false); const [isFormChanged, setIsFormChanged] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { user, hasSomePermissions } = useAuth(); const { user, hasSomePermissions } = useAuth();
@ -34,31 +27,27 @@ export default function BaseSettingPage() {
setIsFormChanged(true); setIsFormChanged(true);
} }
function onResetClick() { function onResetClick() {
if (!form) if (!form) return;
return
if (!baseSetting) { if (!baseSetting) {
form.resetFields(); form.resetFields();
} else { } else {
form.resetFields(); form.resetFields();
form.setFieldsValue(baseSetting); form.setFieldsValue(baseSetting);
} }
setIsFormChanged(false); setIsFormChanged(false);
} }
function onSaveClick() { function onSaveClick() {
if (form) if (form) form.submit();
form.submit();
} }
async function onSubmit(values: BaseSetting) { async function onSubmit(values: BaseSetting) {
setLoading(true); setLoading(true);
try { try {
await update.mutateAsync({ await update.mutateAsync({
where: { where: {
slug: AppConfigSlug.BASE_SETTING, slug: AppConfigSlug.BASE_SETTING,
}, },
data: { meta: JSON.stringify(values) } data: { meta: JSON.stringify(values) },
}); });
setIsFormChanged(false); setIsFormChanged(false);
message.success("已保存"); message.success("已保存");
@ -70,7 +59,6 @@ export default function BaseSettingPage() {
} }
useEffect(() => { useEffect(() => {
if (baseSetting && form) { if (baseSetting && form) {
form.setFieldsValue(baseSetting); form.setFieldsValue(baseSetting);
} }
}, [baseSetting, form]); }, [baseSetting, form]);
@ -101,7 +89,6 @@ export default function BaseSettingPage() {
!hasSomePermissions(RolePerms.MANAGE_BASE_SETTING) !hasSomePermissions(RolePerms.MANAGE_BASE_SETTING)
} }
onFinish={onSubmit} onFinish={onSubmit}
onFieldsChange={handleFieldsChange} onFieldsChange={handleFieldsChange}
layout="vertical"> layout="vertical">
{/* <div {/* <div
@ -127,6 +114,17 @@ export default function BaseSettingPage() {
<Input></Input> <Input></Input>
</Form.Item> </Form.Item>
</div> </div>
<div className="p-2 grid grid-cols-8 gap-2 border-b">
<Form.Item
label="网站logo"
name={["appConfig", "logo"]}>
<AvatarUploader
style={{
width: 192,
height: 108,
}}></AvatarUploader>
</Form.Item>
</div>
{/* <div {/* <div
className="p-2 border-b flex items-center justify-between" className="p-2 border-b flex items-center justify-between"
style={{ style={{
@ -171,7 +169,8 @@ export default function BaseSettingPage() {
</Button> </Button>
</div> </div>
{<div {
<div
className="p-2 border-b text-primary flex justify-between items-center" className="p-2 border-b text-primary flex justify-between items-center"
style={{ style={{
fontSize: token.fontSize, fontSize: token.fontSize,
@ -179,9 +178,12 @@ export default function BaseSettingPage() {
}}> }}>
<span>app在线人数</span> <span>app在线人数</span>
<div> <div>
{clientCount && clientCount > 0 ? `${clientCount}人在线` : '无人在线'} {clientCount && clientCount > 0
? `${clientCount}人在线`
: "无人在线"}
</div> </div>
</div>} </div>
}
</div> </div>
</div> </div>
); );

View File

@ -156,7 +156,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
message: "请输入有效的证件号5-12位数字", message: "请输入有效的证件号5-12位数字",
}, },
]}> ]}>
<Input placeholder="证件号(可选)" /> <Input placeholder="证件号" />
</Form.Item> </Form.Item>
<Form.Item noStyle name={"email"}> <Form.Item noStyle name={"email"}>
<Input <Input

View File

@ -16,6 +16,7 @@ interface UploadingFile {
progress: number; progress: number;
status: "uploading" | "done" | "error"; status: "uploading" | "done" | "error";
fileId?: string; fileId?: string;
url?: string;
fileKey?: string; fileKey?: string;
} }
@ -28,6 +29,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
}) => { }) => {
const { handleFileUpload, uploadProgress } = useTusUpload(); const { handleFileUpload, uploadProgress } = useTusUpload();
const [file, setFile] = useState<UploadingFile | null>(null); const [file, setFile] = useState<UploadingFile | null>(null);
const [previewUrl, setPreviewUrl] = useState<string>(value || ""); const [previewUrl, setPreviewUrl] = useState<string>(value || "");
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -56,7 +58,9 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
progress: 100, progress: 100,
status: "done", status: "done",
fileId: result.fileId, fileId: result.fileId,
url: result?.url,
})); }));
setPreviewUrl(result?.url);
resolve(result.fileId); resolve(result.fileId);
}, },
(error) => { (error) => {
@ -65,7 +69,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
file?.fileKey file?.fileKey
); );
}); });
setPreviewUrl(`${env.SERVER_IP}/uploads/${fileId}`); setPreviewUrl(`http://${env.SERVER_IP}/uploads/${fileId}`);
onChange?.(fileId); onChange?.(fileId);
message.success("头像上传成功"); message.success("头像上传成功");
} catch (error) { } catch (error) {
@ -90,6 +94,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
background: token.colorBgContainer, background: token.colorBgContainer,
...style, // 应用外部传入的样式 ...style, // 应用外部传入的样式
}}> }}>
<div>{previewUrl}</div>
<input <input
type="file" type="file"
ref={inputRef} ref={inputRef}

View File

@ -14,6 +14,7 @@ import { PostDto, PostStateLabels } from "@nice/common";
import dayjs from "dayjs"; import dayjs from "dayjs";
import PostLikeButton from "./detail/PostHeader/PostLikeButton"; import PostLikeButton from "./detail/PostHeader/PostLikeButton";
import { LetterBadge } from "./LetterBadge"; import { LetterBadge } from "./LetterBadge";
import PostHateButton from "./detail/PostHeader/PostHateButton";
const { Title, Paragraph, Text } = Typography; const { Title, Paragraph, Text } = Typography;
interface LetterCardProps { interface LetterCardProps {
@ -117,6 +118,7 @@ export function LetterCard({ letter }: LetterCardProps) {
{letter.views} {letter.views}
</Button> </Button>
<PostLikeButton post={letter as any}></PostLikeButton> <PostLikeButton post={letter as any}></PostLikeButton>
<PostHateButton post={letter as any}></PostHateButton>
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,10 +6,16 @@ import { Avatar } from "antd";
import { useVisitor } from "@nice/client"; import { useVisitor } from "@nice/client";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import { PostDetailContext } from "./context/PostDetailContext"; import { PostDetailContext } from "./context/PostDetailContext";
import { CheckCircleOutlined, CheckOutlined, LikeFilled, LikeOutlined } from "@ant-design/icons"; import {
CheckCircleOutlined,
CheckOutlined,
LikeFilled,
LikeOutlined,
} from "@ant-design/icons";
import PostLikeButton from "./PostHeader/PostLikeButton"; import PostLikeButton from "./PostHeader/PostLikeButton";
import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar"; import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar";
import PostResources from "./PostResources"; import PostResources from "./PostResources";
import PostHateButton from "./PostHeader/PostHateButton";
export default function PostCommentCard({ export default function PostCommentCard({
post, post,
@ -57,6 +63,8 @@ export default function PostCommentCard({
<span className=" text-sm text-slate-500">{`#${index + 1}`}</span> <span className=" text-sm text-slate-500">{`#${index + 1}`}</span>
<PostLikeButton <PostLikeButton
post={post}></PostLikeButton> post={post}></PostLikeButton>
<PostHateButton
post={post}></PostHateButton>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,52 @@
import { PostDto, VisitType } from "@nice/common";
import { useVisitor } from "@nice/client";
import { Button, Tooltip } from "antd";
import { DislikeFilled, DislikeOutlined } from "@ant-design/icons";
import { useAuth } from "@web/src/providers/auth-provider";
export default function PostHateButton({ post }: { post: PostDto }) {
const { user } = useAuth();
const { hate, unHate } = useVisitor();
function hateThisPost() {
if (!post?.hated) {
post.hates += 1;
post.hated = true;
hate.mutateAsync({
data: {
visitorId: user?.id || null,
postId: post.id,
type: VisitType.HATE,
},
});
} else {
post.hates -= 1;
post.hated = false;
unHate.mutateAsync({
where: {
visitorId: user?.id || null,
postId: post.id,
type: VisitType.HATE,
},
});
}
}
return (
<Button
title={post?.hated ? "取消点踩" : "点踩"}
type={post?.hated ? "primary" : "default"}
style={{
backgroundColor: post?.hated ? "#ff4d4f" : "#fff",
borderColor: "#ff4d4f",
color: post?.hated ? "#fff" : "#ff4d4f",
}}
shape="round"
icon={post?.hated ? <DislikeFilled /> : <DislikeOutlined />}
onClick={(e) => {
e.stopPropagation();
hateThisPost();
}}>
<span className="mr-1"></span>
{post?.hates || 0}
</Button>
);
}

View File

@ -10,6 +10,7 @@ import { Button, Tooltip } from "antd/lib";
import { PostDetailContext } from "../context/PostDetailContext"; import { PostDetailContext } from "../context/PostDetailContext";
import PostLikeButton from "./PostLikeButton"; import PostLikeButton from "./PostLikeButton";
import PostResources from "../PostResources"; import PostResources from "../PostResources";
import PostHateButton from "./PostHateButton";
export function StatsSection() { export function StatsSection() {
const { post } = useContext(PostDetailContext); const { post } = useContext(PostDetailContext);
@ -26,6 +27,7 @@ export function StatsSection() {
<span className="mr-1"></span>{post?.commentsCount} <span className="mr-1"></span>{post?.commentsCount}
</Button> </Button>
<PostLikeButton post={post}></PostLikeButton> <PostLikeButton post={post}></PostLikeButton>
<PostHateButton post={post}></PostHateButton>
</div> </div>
</div> </div>

View File

@ -3,15 +3,15 @@ import { AppConfigSlug, BaseSetting } from "@nice/common";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
export function useAppConfig() { export function useAppConfig() {
const utils = api.useUtils() const utils = api.useUtils();
const [baseSetting, setBaseSetting] = useState<BaseSetting | undefined>(); const [baseSetting, setBaseSetting] = useState<BaseSetting | undefined>();
const { data, isLoading }: { data: any; isLoading: boolean } = const { data, isLoading }: { data: any; isLoading: boolean } =
api.app_config.findFirst.useQuery({ api.app_config.findFirst.useQuery({
where: { slug: AppConfigSlug.BASE_SETTING } where: { slug: AppConfigSlug.BASE_SETTING },
}); });
const handleMutationSuccess = useCallback(() => { const handleMutationSuccess = useCallback(() => {
utils.app_config.invalidate() utils.app_config.invalidate();
}, [utils]); }, [utils]);
// Use the generic success handler in mutations // Use the generic success handler in mutations
@ -28,7 +28,6 @@ export function useAppConfig() {
if (data?.meta) { if (data?.meta) {
setBaseSetting(JSON.parse(data?.meta)); setBaseSetting(JSON.parse(data?.meta));
} }
}, [data, isLoading]); }, [data, isLoading]);
const splashScreen = useMemo(() => { const splashScreen = useMemo(() => {
return baseSetting?.appConfig?.splashScreen; return baseSetting?.appConfig?.splashScreen;
@ -36,8 +35,10 @@ export function useAppConfig() {
const devDept = useMemo(() => { const devDept = useMemo(() => {
return baseSetting?.appConfig?.devDept; return baseSetting?.appConfig?.devDept;
}, [baseSetting]); }, [baseSetting]);
const logo = useMemo(() => {
return baseSetting?.appConfig?.logo;
}, [baseSetting]);
return { return {
create, create,
deleteMany, deleteMany,
update, update,
@ -45,5 +46,6 @@ export function useAppConfig() {
splashScreen, splashScreen,
devDept, devDept,
isLoading, isLoading,
logo,
}; };
} }

View File

@ -171,5 +171,7 @@ export function useVisitor() {
deleteStar, deleteStar,
like, like,
unLike, unLike,
hate,
unHate,
}; };
} }

View File

@ -8,6 +8,7 @@ export const postDetailSelect: Prisma.PostSelect = {
content: true, content: true,
views: true, views: true,
likes: true, likes: true,
hates: true,
isPublic: true, isPublic: true,
resources: true, resources: true,
createdAt: true, createdAt: true,

View File

@ -39,11 +39,11 @@ export type StaffDto = Staff & {
domain?: Department; domain?: Department;
department?: Department; department?: Department;
meta?: { meta?: {
photoUrl?: string photoUrl?: string;
office?: string office?: string;
email?: string email?: string;
rank?: string rank?: string;
} };
}; };
export interface AuthDto { export interface AuthDto {
token: string; token: string;
@ -133,6 +133,7 @@ export type PostComment = {
export type PostDto = Post & { export type PostDto = Post & {
readed: boolean; readed: boolean;
liked: boolean; liked: boolean;
hated: boolean;
readedCount: number; readedCount: number;
commentsCount: number; commentsCount: number;
terms: TermDto[]; terms: TermDto[];
@ -167,6 +168,7 @@ export interface BaseSetting {
appConfig?: { appConfig?: {
splashScreen?: string; splashScreen?: string;
devDept?: string; devDept?: string;
logo?: string;
}; };
} }
export interface PostMeta { export interface PostMeta {