├── packages ├── types │ ├── lib │ │ ├── esm │ │ │ ├── image.js │ │ │ ├── post.js │ │ │ ├── site.js │ │ │ ├── tag.js │ │ │ ├── visitor.js │ │ │ ├── category.js │ │ │ ├── userRole.js │ │ │ ├── invitationCode.js │ │ │ ├── rolePermission.js │ │ │ ├── svg.js │ │ │ ├── index.js │ │ │ ├── comment.js │ │ │ ├── page.js │ │ │ └── link.js │ │ └── cjs │ │ │ ├── post.js │ │ │ ├── site.js │ │ │ ├── tag.js │ │ │ ├── category.js │ │ │ ├── image.js │ │ │ ├── userRole.js │ │ │ ├── visitor.js │ │ │ ├── invitationCode.js │ │ │ ├── rolePermission.js │ │ │ ├── svg.js │ │ │ ├── comment.js │ │ │ ├── page.js │ │ │ └── link.js │ ├── src │ │ ├── tag.ts │ │ ├── category.ts │ │ ├── svg.ts │ │ ├── invitationCode.ts │ │ ├── image.ts │ │ ├── visitor.ts │ │ ├── rolePermission.ts │ │ ├── link.ts │ │ └── index.ts │ ├── types │ │ ├── tag.d.ts │ │ ├── category.d.ts │ │ ├── invitationCode.d.ts │ │ ├── svg.d.ts │ │ ├── image.d.ts │ │ ├── visitor.d.ts │ │ ├── rolePermission.d.ts │ │ ├── index.d.ts │ │ └── link.d.ts │ ├── rollup.config.js │ ├── .eslintrc.json │ ├── tsconfig.json │ └── package.json ├── components │ ├── global.d.ts │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── SvgBox │ │ │ │ └── index.tsx │ │ │ └── Portal │ │ │ │ └── index.tsx │ │ ├── styles │ │ │ └── index.module.css │ │ └── index.ts │ ├── types │ │ ├── components │ │ │ ├── constants.d.ts │ │ │ ├── SvgBox │ │ │ │ └── index.d.ts │ │ │ ├── Portal │ │ │ │ └── index.d.ts │ │ │ ├── SvgContainer │ │ │ │ └── index.d.ts │ │ │ └── Icon │ │ │ │ └── index.d.ts │ │ └── index.d.ts │ ├── .eslintrc.json │ └── tsconfig.json ├── hooks │ ├── .gitignore │ ├── .eslintrc.json │ ├── types │ │ ├── useMount.d.ts │ │ ├── src │ │ │ ├── useMount.d.ts │ │ │ ├── useDebounce.d.ts │ │ │ ├── utils │ │ │ │ └── index.d.ts │ │ │ ├── useOutsideClick.d.ts │ │ │ ├── useSize.d.ts │ │ │ ├── useResize.d.ts │ │ │ ├── usePrevious.d.ts │ │ │ └── useScroll.d.ts │ │ ├── useDebounce.d.ts │ │ ├── utils │ │ │ └── index.d.ts │ │ ├── useOutsideClick.d.ts │ │ ├── useSize.d.ts │ │ ├── useResize.d.ts │ │ ├── usePrevious.d.ts │ │ ├── useDisabledScrollByMask.d.ts │ │ ├── useScroll.d.ts │ │ └── index.d.ts │ ├── rollup.config.js │ ├── tsconfig.json │ └── src │ │ ├── utils │ │ └── index.ts │ │ ├── useMount.ts │ │ ├── useDebounce.ts │ │ ├── index.ts │ │ ├── usePrevious.ts │ │ └── useResize.ts ├── utils │ ├── .gitignore │ ├── .babelrc │ ├── types │ │ ├── isWindow.d.ts │ │ ├── isDocument.d.ts │ │ ├── isPc.d.ts │ │ ├── delay.d.ts │ │ ├── filterXSS.d.ts │ │ ├── noopPromise.d.ts │ │ ├── canUseDom.d.ts │ │ ├── highlight.d.ts │ │ ├── parseMarkdown.d.ts │ │ ├── randomInteger.d.ts │ │ ├── getOffsetLeft.d.ts │ │ ├── getOffsetTop.d.ts │ │ ├── bindClickOutSide.d.ts │ │ ├── formatDate.d.ts │ │ ├── getOS.d.ts │ │ ├── theme.d.ts │ │ ├── checkStr.d.ts │ │ ├── cacheLocal.d.ts │ │ ├── cacheCookie.d.ts │ │ └── cacheSession.d.ts │ ├── .eslintrc.json │ ├── rollup.config.js │ ├── src │ │ ├── isWindow.ts │ │ ├── isDocument.ts │ │ ├── randomInteger.ts │ │ ├── noopPromise.ts │ │ ├── canUseDom.ts │ │ ├── isPc.ts │ │ ├── delay.ts │ │ ├── formatDate.ts │ │ ├── filterXSS.ts │ │ ├── getOffsetLeft.ts │ │ ├── getOffsetTop.ts │ │ ├── highlight.ts │ │ ├── bindClickOutSide.ts │ │ └── getOS.ts │ └── tsconfig.json ├── constants │ ├── .gitignore │ ├── .eslintrc.json │ ├── rollup.config.js │ └── tsconfig.json ├── tsconfig │ ├── .gitignore │ ├── react-library.config.json │ ├── package.json │ ├── library.config.json │ ├── tsconfig.json │ ├── web.config.json │ ├── server.config.json │ └── base.config.json ├── eslint-config │ ├── .gitignore │ ├── common-ts.js │ ├── web.js │ └── server.js ├── prettier-config │ ├── .gitignore │ ├── index.js │ └── package.json └── cli │ ├── bin │ ├── replace.js │ └── lib.js │ └── lib │ └── replaceWorkspace.js ├── .npmrc ├── apps ├── server │ ├── src │ │ ├── modules │ │ │ ├── role │ │ │ │ ├── dto │ │ │ │ │ ├── response.dto.ts │ │ │ │ │ └── request.dto.ts │ │ │ │ └── role.module.ts │ │ │ ├── permission │ │ │ │ ├── dto │ │ │ │ │ ├── response.dto.ts │ │ │ │ │ └── request.dto.ts │ │ │ │ ├── permission.module.ts │ │ │ │ └── prisma │ │ │ │ │ └── permission.prisma │ │ │ ├── mongodb │ │ │ │ ├── types │ │ │ │ │ ├── blogLike.ts │ │ │ │ │ ├── blogComment.ts │ │ │ │ │ ├── userSystem.ts │ │ │ │ │ ├── blogTag.ts │ │ │ │ │ ├── visitor.ts │ │ │ │ │ ├── github.ts │ │ │ │ │ ├── user.ts │ │ │ │ │ ├── comment.ts │ │ │ │ │ ├── link.ts │ │ │ │ │ ├── play.ts │ │ │ │ │ └── blog.ts │ │ │ │ ├── mongodb.module.ts │ │ │ │ └── models │ │ │ │ │ ├── counter.ts │ │ │ │ │ ├── visitor.ts │ │ │ │ │ ├── blogLike.ts │ │ │ │ │ ├── blogTag.ts │ │ │ │ │ ├── userSystem.ts │ │ │ │ │ ├── user.ts │ │ │ │ │ ├── comment.ts │ │ │ │ │ ├── github.ts │ │ │ │ │ └── play.ts │ │ │ ├── tag │ │ │ │ ├── prisma │ │ │ │ │ └── tag.prisma │ │ │ │ ├── tag.module.ts │ │ │ │ ├── tag.public.controller.ts │ │ │ │ └── dto │ │ │ │ │ └── request.dto.ts │ │ │ ├── visitor │ │ │ │ ├── dto │ │ │ │ │ └── request.dto.ts │ │ │ │ ├── prisma │ │ │ │ │ └── visitor.prisma │ │ │ │ ├── visitor.module.ts │ │ │ │ └── visitor.public.controller.ts │ │ │ ├── http │ │ │ │ └── http.module.ts │ │ │ ├── category │ │ │ │ ├── prisma │ │ │ │ │ └── category.prisma │ │ │ │ ├── category.module.ts │ │ │ │ └── dto │ │ │ │ │ └── request.dto.ts │ │ │ ├── prisma │ │ │ │ ├── config.prisma │ │ │ │ ├── prisma.module.ts │ │ │ │ └── prisma.service.ts │ │ │ ├── svg │ │ │ │ ├── prisma │ │ │ │ │ └── svg.prisma │ │ │ │ ├── svg.module.ts │ │ │ │ ├── dto │ │ │ │ │ └── request.dto.ts │ │ │ │ └── svg.public.controller.ts │ │ │ ├── globalConfig │ │ │ │ └── globalConfig.module.ts │ │ │ ├── image │ │ │ │ ├── image.module.ts │ │ │ │ ├── prisma │ │ │ │ │ └── image.prisma │ │ │ │ └── dto │ │ │ │ │ └── request.dto.ts │ │ │ ├── email │ │ │ │ ├── email.module.ts │ │ │ │ └── email.controller.ts │ │ │ ├── init │ │ │ │ ├── init.public.controller.ts │ │ │ │ ├── init.controller.ts │ │ │ │ └── init.module.ts │ │ │ ├── link │ │ │ │ ├── prisma │ │ │ │ │ └── link.prisma │ │ │ │ ├── link.module.ts │ │ │ │ └── link.public.controller.ts │ │ │ ├── wechaty │ │ │ │ └── wechaty.module.ts │ │ │ ├── invitationCode │ │ │ │ ├── invitationCode.module.ts │ │ │ │ ├── dto │ │ │ │ │ └── request.dto.ts │ │ │ │ └── prisma │ │ │ │ │ └── invitationCode.prisma │ │ │ ├── page │ │ │ │ └── page.module.ts │ │ │ ├── post │ │ │ │ └── post.module.ts │ │ │ ├── appConfig │ │ │ │ └── appConfig.module.ts │ │ │ ├── comment │ │ │ │ └── comment.module.ts │ │ │ ├── shared │ │ │ │ └── shared.module.ts │ │ │ ├── site │ │ │ │ └── site.module.ts │ │ │ └── auth │ │ │ │ └── strategies │ │ │ │ ├── local.strategy.ts │ │ │ │ └── jwt.strategy.ts │ │ ├── config │ │ │ ├── configuration.ts │ │ │ └── metaData.ts │ │ ├── utils │ │ │ ├── ip.ts │ │ │ ├── ua.ts │ │ │ └── image-type.ts │ │ ├── decorators │ │ │ └── public.ts │ │ ├── guards │ │ │ └── localAuth.guard.ts │ │ ├── app.service.ts │ │ ├── dtos │ │ │ └── pagination.dto.ts │ │ ├── pipes │ │ │ └── pagination.ts │ │ ├── interceptors │ │ │ └── globalResponseInterceptor.ts │ │ └── app.controller.ts │ ├── .dockerignore │ ├── .eslintrc.json │ ├── tsconfig.build.json │ ├── prisma │ │ └── migrations │ │ │ └── migration_lock.toml │ ├── README.md │ ├── nest-cli.json │ ├── tsconfig.json │ └── Dockerfile ├── admin │ ├── .dockerignore │ ├── src │ │ ├── styles │ │ │ ├── markdown │ │ │ │ └── index.css │ │ │ ├── utilities.css │ │ │ └── index.css │ │ ├── utils │ │ │ └── features.ts │ │ ├── app │ │ │ ├── (noauth) │ │ │ │ ├── test │ │ │ │ │ ├── Tree │ │ │ │ │ │ ├── Item │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ ├── Handle │ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ │ └── Handle.tsx │ │ │ │ │ │ │ │ ├── Remove │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ ├── Action │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── TreeItem │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── DndKit │ │ │ │ │ │ ├── Droppable.tsx │ │ │ │ │ │ └── Draggable.tsx │ │ │ │ ├── restore │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── login │ │ │ │ │ └── style.module.css │ │ │ ├── (auth) │ │ │ │ ├── dashboard │ │ │ │ │ ├── echarts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── Pie.tsx │ │ │ │ │ │ ├── PieFull.tsx │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── Card │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── setting-profile │ │ │ │ │ └── style.module.css │ │ │ │ ├── setting │ │ │ │ │ ├── user │ │ │ │ │ │ └── style.module.css │ │ │ │ │ └── layout.tsx │ │ │ │ ├── role-permission │ │ │ │ │ ├── role │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── user │ │ │ │ │ │ └── types.ts │ │ │ │ ├── loading.tsx │ │ │ │ ├── GlobalData.tsx │ │ │ │ ├── page │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── menu │ │ │ │ │ │ └── style.module.css │ │ │ │ └── media │ │ │ │ │ └── image │ │ │ │ │ ├── style.module.css │ │ │ │ │ └── Card.tsx │ │ │ ├── Motion.tsx │ │ │ ├── Provider.tsx │ │ │ └── globals.css │ │ ├── api │ │ │ ├── init.ts │ │ │ ├── index.ts │ │ │ ├── visitor.ts │ │ │ └── comment.ts │ │ ├── components │ │ │ ├── FormList │ │ │ │ ├── style.module.css │ │ │ │ └── index.tsx │ │ │ ├── PageSetting │ │ │ │ └── style.module.css │ │ │ ├── Image │ │ │ │ ├── index.tsx │ │ │ │ └── NormalImage.tsx │ │ │ ├── ImageList │ │ │ │ └── style.module.css │ │ │ ├── FormItem │ │ │ │ └── style.module.css │ │ │ ├── Editor │ │ │ │ ├── style.module.css │ │ │ │ └── plugins │ │ │ │ │ ├── rawHTML.tsx │ │ │ │ │ ├── linkTarget.tsx │ │ │ │ │ └── heading.tsx │ │ │ ├── DndList │ │ │ │ └── index.tsx │ │ │ ├── Layout │ │ │ │ ├── index.tsx │ │ │ │ └── style.module.css │ │ │ ├── PostEditor │ │ │ │ └── style.module.css │ │ │ ├── Button │ │ │ │ └── ButtonTableText.tsx │ │ │ ├── PanelSetting │ │ │ │ ├── style.module.css │ │ │ │ └── Title.tsx │ │ │ ├── FormImageList │ │ │ │ └── index.tsx │ │ │ ├── ImageInfo │ │ │ │ └── style.module.css │ │ │ ├── FormText │ │ │ │ └── index.tsx │ │ │ ├── Table │ │ │ │ └── index.tsx │ │ │ └── FormUpload │ │ │ │ └── index.tsx │ │ ├── middlewares │ │ │ ├── types.ts │ │ │ ├── composeMiddlewares.ts │ │ │ └── proxyMiddleware.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── context │ │ │ ├── helper │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── provider.tsx │ │ │ ├── global │ │ │ │ ├── index.ts │ │ │ │ └── context.ts │ │ │ └── index.ts │ │ ├── middleware.ts │ │ └── config │ │ │ └── metaData.ts │ ├── .eslintrc.json │ ├── public │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── favicon.png │ │ └── vercel.svg │ ├── postcss.config.js │ ├── Dockerfile │ ├── .gitignore │ └── tsconfig.json └── client │ ├── .dockerignore │ ├── global.d.ts │ ├── .eslintrc.json │ ├── src │ ├── styles │ │ ├── base │ │ │ ├── index.css │ │ │ └── size.css │ │ ├── markdown │ │ │ └── index.css │ │ ├── components │ │ │ ├── line.css │ │ │ └── shadow.css │ │ └── utilities │ │ │ ├── index.css │ │ │ ├── transition.css │ │ │ ├── mask.css │ │ │ ├── safe.css │ │ │ └── background.css │ ├── components │ │ ├── Panel │ │ │ └── index.ts │ │ ├── Modal │ │ │ └── style.module.css │ │ ├── Image │ │ │ ├── index.tsx │ │ │ └── NormalImage.tsx │ │ ├── Tooltip │ │ │ └── style.module.css │ │ ├── Loading │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── PreviewImage │ │ │ └── style.module.css │ │ ├── Link │ │ │ └── index.tsx │ │ ├── Popup │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── Github │ │ │ └── style.css │ │ ├── BackTop │ │ │ └── style.module.css │ │ ├── LayoutContainer │ │ │ └── index.tsx │ │ ├── ALink │ │ │ └── index.tsx │ │ └── PanelArrow │ │ │ └── index.tsx │ ├── utils │ │ └── features.ts │ ├── app │ │ ├── (other) │ │ │ ├── test │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── not-found.tsx │ │ ├── apis │ │ │ ├── health-check │ │ │ │ └── route.ts │ │ │ └── clear-cache │ │ │ │ └── route.ts │ │ └── (main) │ │ │ ├── post │ │ │ └── [id] │ │ │ │ ├── constants.ts │ │ │ │ ├── error.tsx │ │ │ │ └── Meta │ │ │ │ └── index.tsx │ │ │ ├── page.tsx │ │ │ ├── aboutme │ │ │ ├── Me │ │ │ │ └── style.module.css │ │ │ └── page.tsx │ │ │ ├── page │ │ │ └── [page] │ │ │ │ └── page.tsx │ │ │ ├── messages │ │ │ ├── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page │ │ │ │ └── [page] │ │ │ │ ├── UpdatePagination.tsx │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ ├── search │ │ │ ├── page.tsx │ │ │ └── page │ │ │ │ └── [page] │ │ │ │ └── page.tsx │ │ │ ├── tag │ │ │ └── [...slug] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── category │ │ │ └── [...slug] │ │ │ └── page.tsx │ ├── proComponents │ │ ├── PostCard │ │ │ ├── index.tsx │ │ │ └── ColCard │ │ │ │ └── styles.module.css │ │ ├── Banner │ │ │ ├── components │ │ │ │ ├── Bubble │ │ │ │ │ └── types.ts │ │ │ │ ├── Cover │ │ │ │ │ └── index.tsx │ │ │ │ ├── Image │ │ │ │ │ └── index.tsx │ │ │ │ ├── Size │ │ │ │ │ └── index.tsx │ │ │ │ ├── Swiper │ │ │ │ │ └── Button │ │ │ │ │ │ └── index.tsx │ │ │ │ └── Card │ │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── NavigationSidebar │ │ │ ├── contants.ts │ │ │ ├── PC │ │ │ │ ├── index.ts │ │ │ │ └── withContext.tsx │ │ │ ├── index.ts │ │ │ └── components │ │ │ │ └── Tags │ │ │ │ └── index.tsx │ │ ├── PostCatalog │ │ │ ├── hooks │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── Comment │ │ │ ├── components │ │ │ │ ├── CommentBox │ │ │ │ │ └── style.module.css │ │ │ │ ├── Emoji │ │ │ │ │ └── styles.module.css │ │ │ │ └── Meta │ │ │ │ │ └── index.tsx │ │ │ ├── State.tsx │ │ │ ├── store │ │ │ │ ├── index.ts │ │ │ │ └── context.ts │ │ │ └── types.ts │ │ ├── GlobalData │ │ │ ├── index.tsx │ │ │ ├── Update.tsx │ │ │ └── GetData.tsx │ │ ├── PostPage │ │ │ └── Carousel │ │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── MIcon │ │ │ │ └── index.tsx │ │ │ ├── SearchBtn │ │ │ │ └── index.tsx │ │ │ └── Logo │ │ │ │ └── index.tsx │ │ └── PostContent │ │ │ └── index.tsx │ ├── middlewares │ │ ├── types.ts │ │ ├── composeMiddlewares.ts │ │ └── proxyMiddleware.ts │ ├── api │ │ ├── svg.ts │ │ ├── tag.ts │ │ ├── index.ts │ │ ├── link.ts │ │ ├── visitor.ts │ │ └── page.ts │ ├── config │ │ ├── index.ts │ │ └── metaData.ts │ ├── middleware.ts │ ├── context │ │ ├── global │ │ │ ├── index.ts │ │ │ └── hooks │ │ │ │ └── usePc.ts │ │ ├── helper │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ └── provider.tsx │ │ ├── page │ │ │ ├── index.ts │ │ │ └── context.ts │ │ └── index.ts │ ├── constants │ │ └── index.ts │ └── hooks │ │ ├── useAutoScrollAnchor.ts │ │ ├── usePreviewImage.ts │ │ ├── useResizeValue.ts │ │ ├── useMainHeight.ts │ │ └── useHash.ts │ ├── public │ ├── links.jpg │ ├── search.jpg │ ├── messages.jpg │ ├── image-fail.png │ ├── image-loading.gif │ └── fonts │ │ ├── Play-bold.ttf │ │ ├── Play-regular.ttf │ │ ├── maokentangyuan.woff2 │ │ ├── PangMenZhengDaoBiaoTiTi.woff2 │ │ └── font_2533226_rc5qpwobx9c.woff2 │ ├── README.md │ ├── postcss.config.js │ ├── tsconfig.json │ ├── .gitignore │ └── Dockerfile ├── .dockerignore ├── pnpm-workspace.yaml ├── .husky ├── commit-msg └── pre-commit ├── scripts ├── prisma-dev.sh └── git.js ├── commitlint.config.js ├── docker └── README.md ├── .changeset ├── config.json └── README.md ├── opts └── write-image.sh ├── README.md └── .gitignore /packages/types/lib/esm/image.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/post.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/site.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/tag.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/visitor.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/category.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/userRole.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /apps/server/src/modules/role/dto/response.dto.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/types/lib/esm/invitationCode.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/esm/rolePermission.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .gitignore 4 | dist -------------------------------------------------------------------------------- /apps/server/src/modules/permission/dto/response.dto.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/image.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/userRole.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/visitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/components/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/invitationCode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/rolePermission.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /apps/admin/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .turbo 4 | out -------------------------------------------------------------------------------- /apps/client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .turbo 4 | out -------------------------------------------------------------------------------- /packages/hooks/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | /lib 5 | 6 | -------------------------------------------------------------------------------- /packages/utils/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | /lib 5 | 6 | -------------------------------------------------------------------------------- /apps/admin/src/styles/markdown/index.css: -------------------------------------------------------------------------------- 1 | @import './theme/primary.css'; -------------------------------------------------------------------------------- /apps/client/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | gapi: any; 3 | } 4 | -------------------------------------------------------------------------------- /packages/components/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | /lib 5 | 6 | -------------------------------------------------------------------------------- /packages/constants/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | /lib 5 | 6 | -------------------------------------------------------------------------------- /packages/tsconfig/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/utils/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /apps/server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .turbo 3 | dist 4 | public 5 | logs -------------------------------------------------------------------------------- /packages/eslint-config/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/prettier-config/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/utils/types/isWindow.d.ts: -------------------------------------------------------------------------------- 1 | export default function isWindow(): boolean; 2 | -------------------------------------------------------------------------------- /apps/admin/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/web"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/web"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/client/src/styles/base/index.css: -------------------------------------------------------------------------------- 1 | @import './theme.css'; 2 | @import './size.css'; -------------------------------------------------------------------------------- /packages/components/src/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const ICON_PREFIX = 'icon'; 2 | -------------------------------------------------------------------------------- /packages/utils/types/isDocument.d.ts: -------------------------------------------------------------------------------- 1 | export default function isDocument(): boolean; 2 | -------------------------------------------------------------------------------- /packages/utils/types/isPc.d.ts: -------------------------------------------------------------------------------- 1 | export default function isPc(ua: string): boolean; 2 | -------------------------------------------------------------------------------- /apps/server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/server"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/hooks/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/web"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/utils/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/web"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/utils/rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | external: [/^core-js/], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/components/types/components/constants.d.ts: -------------------------------------------------------------------------------- 1 | export declare const ICON_PREFIX = "icon"; 2 | -------------------------------------------------------------------------------- /packages/utils/types/delay.d.ts: -------------------------------------------------------------------------------- 1 | export default function delay(time: number): Promise; 2 | -------------------------------------------------------------------------------- /apps/admin/src/utils/features.ts: -------------------------------------------------------------------------------- 1 | import { domMax } from 'framer-motion'; 2 | export default domMax; 3 | -------------------------------------------------------------------------------- /apps/client/src/styles/markdown/index.css: -------------------------------------------------------------------------------- 1 | @import './theme/primary.css'; 2 | @import './theme/page.css'; -------------------------------------------------------------------------------- /packages/constants/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/common-ts"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/hooks/types/useMount.d.ts: -------------------------------------------------------------------------------- 1 | export default function useMount(isRender?: boolean): boolean; 2 | -------------------------------------------------------------------------------- /packages/utils/types/filterXSS.d.ts: -------------------------------------------------------------------------------- 1 | export default function filterXSS(content: string): string; 2 | -------------------------------------------------------------------------------- /apps/admin/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/admin/public/logo.png -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/components/Handle/index.ts: -------------------------------------------------------------------------------- 1 | export { Handle } from './Handle'; 2 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/components/Remove/index.ts: -------------------------------------------------------------------------------- 1 | export { Remove } from './Remove'; 2 | -------------------------------------------------------------------------------- /apps/client/src/components/Panel/index.ts: -------------------------------------------------------------------------------- 1 | import PanelLine from './Line'; 2 | 3 | export { PanelLine }; 4 | -------------------------------------------------------------------------------- /apps/client/src/utils/features.ts: -------------------------------------------------------------------------------- 1 | import { domMax } from 'framer-motion'; 2 | export default domMax; 3 | -------------------------------------------------------------------------------- /packages/hooks/types/src/useMount.d.ts: -------------------------------------------------------------------------------- 1 | export default function useMount(isRender?: boolean): boolean; 2 | -------------------------------------------------------------------------------- /packages/utils/types/noopPromise.d.ts: -------------------------------------------------------------------------------- 1 | export default function noopPromiseArray(): Promise; 2 | -------------------------------------------------------------------------------- /apps/admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/admin/public/favicon.ico -------------------------------------------------------------------------------- /apps/admin/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/admin/public/favicon.png -------------------------------------------------------------------------------- /apps/client/public/links.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/links.jpg -------------------------------------------------------------------------------- /apps/client/public/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/search.jpg -------------------------------------------------------------------------------- /packages/utils/types/canUseDom.d.ts: -------------------------------------------------------------------------------- 1 | declare const canUseDom: () => boolean; 2 | export default canUseDom; 3 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/echarts/index.ts: -------------------------------------------------------------------------------- 1 | import { echarts } from './echarts'; 2 | export { echarts }; 3 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/components/index.ts: -------------------------------------------------------------------------------- 1 | export { TreeItem, SortableTreeItem } from './TreeItem'; 2 | -------------------------------------------------------------------------------- /apps/client/public/messages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/messages.jpg -------------------------------------------------------------------------------- /packages/types/src/tag.ts: -------------------------------------------------------------------------------- 1 | export interface Tag { 2 | id: number; 3 | name: string; 4 | alias: string; 5 | } 6 | -------------------------------------------------------------------------------- /apps/client/public/image-fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/image-fail.png -------------------------------------------------------------------------------- /packages/constants/rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | external: [/^core-js/, /^@funblog\/types(?:.+)?$/], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/utils/src/isWindow.ts: -------------------------------------------------------------------------------- 1 | export default function isWindow() { 2 | return typeof window !== 'undefined'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/utils/types/highlight.d.ts: -------------------------------------------------------------------------------- 1 | import highlightJs from 'highlight.js/lib/core'; 2 | export default highlightJs; 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /apps/client/public/image-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/image-loading.gif -------------------------------------------------------------------------------- /packages/types/src/category.ts: -------------------------------------------------------------------------------- 1 | export interface Category { 2 | id: string; 3 | name: string; 4 | alias: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/types/types/tag.d.ts: -------------------------------------------------------------------------------- 1 | export interface Tag { 2 | id: number; 3 | name: string; 4 | alias: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/utils/src/isDocument.ts: -------------------------------------------------------------------------------- 1 | export default function isDocument() { 2 | return typeof document !== 'undefined'; 3 | } 4 | -------------------------------------------------------------------------------- /apps/client/public/fonts/Play-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/fonts/Play-bold.ttf -------------------------------------------------------------------------------- /apps/client/src/app/(other)/test/page.tsx: -------------------------------------------------------------------------------- 1 | function Test() { 2 | return
Test
; 3 | } 4 | 5 | export default Test; 6 | -------------------------------------------------------------------------------- /apps/client/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | function NotFound() { 2 | return
NotFound
; 3 | } 4 | 5 | export default NotFound; 6 | -------------------------------------------------------------------------------- /packages/components/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/web"], 3 | "ignorePatterns": ["**/*.d.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utils/types/parseMarkdown.d.ts: -------------------------------------------------------------------------------- 1 | declare function parseMarkdown(text: string): string; 2 | export default parseMarkdown; 3 | -------------------------------------------------------------------------------- /apps/client/public/fonts/Play-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/fonts/Play-regular.ttf -------------------------------------------------------------------------------- /packages/hooks/types/useDebounce.d.ts: -------------------------------------------------------------------------------- 1 | declare function useDebounce(value: T, delay?: number): T; 2 | export default useDebounce; 3 | -------------------------------------------------------------------------------- /packages/types/types/category.d.ts: -------------------------------------------------------------------------------- 1 | export interface Category { 2 | id: string; 3 | name: string; 4 | alias: string; 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/restore/page.tsx: -------------------------------------------------------------------------------- 1 | function Restore() { 2 | return
待开发...
; 3 | } 4 | 5 | export default Restore; 6 | -------------------------------------------------------------------------------- /apps/client/README.md: -------------------------------------------------------------------------------- 1 | ### Error 2 | 3 | 1. fetch 请求 会导致这个问题 / chrome代理冲突 4 | > Failed to fetch RSC payload. Falling back to browser navigation. -------------------------------------------------------------------------------- /apps/client/public/fonts/maokentangyuan.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/fonts/maokentangyuan.woff2 -------------------------------------------------------------------------------- /packages/hooks/types/src/useDebounce.d.ts: -------------------------------------------------------------------------------- 1 | declare function useDebounce(value: T, delay?: number): T; 2 | export default useDebounce; 3 | -------------------------------------------------------------------------------- /packages/utils/types/randomInteger.d.ts: -------------------------------------------------------------------------------- 1 | declare function randomInteger(min: number, max: number): number; 2 | export default randomInteger; 3 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/setting-profile/style.module.css: -------------------------------------------------------------------------------- 1 | .settingProfile { 2 | :global(.ant-form-item) { 3 | margin-bottom: 0; 4 | } 5 | } -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/index.ts: -------------------------------------------------------------------------------- 1 | export { Item } from './Item'; 2 | export { Action, Handle, Remove } from './components'; 3 | -------------------------------------------------------------------------------- /apps/client/src/components/Modal/style.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } -------------------------------------------------------------------------------- /apps/server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utils/types/getOffsetLeft.d.ts: -------------------------------------------------------------------------------- 1 | export default function getOffsetLeft(node: HTMLElement | null, container?: HTMLElement | null): number; 2 | -------------------------------------------------------------------------------- /packages/utils/types/getOffsetTop.d.ts: -------------------------------------------------------------------------------- 1 | export default function getOffsetTop(node: HTMLElement | null, container?: HTMLElement | null): number; 2 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | function Test() { 4 | return
Test
; 5 | } 6 | 7 | export default Test; 8 | -------------------------------------------------------------------------------- /packages/hooks/types/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const debounceFn: (func: (...args: any[]) => void, delay?: number) => (...args: any[]) => void; 2 | -------------------------------------------------------------------------------- /apps/admin/src/api/init.ts: -------------------------------------------------------------------------------- 1 | import { request } from './fetch'; 2 | 3 | export function initData() { 4 | return request.post('/api/init/data'); 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormList/style.module.css: -------------------------------------------------------------------------------- 1 | .proList { 2 | :global { 3 | .ant-pro-table-alert { 4 | display: none; 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /apps/admin/src/components/PageSetting/style.module.css: -------------------------------------------------------------------------------- 1 | .drawer { 2 | :global { 3 | .ant-drawer-body { 4 | padding: 0; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/hooks/types/src/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const debounceFn: (func: (...args: any[]) => void, delay?: number) => (...args: any[]) => void; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # npx lint-staged --allow-empty 5 | pnpm run check && pnpm run lint 6 | 7 | -------------------------------------------------------------------------------- /apps/admin/src/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from './Image'; 2 | import NormalImage from './NormalImage'; 3 | 4 | export { Image, NormalImage }; 5 | -------------------------------------------------------------------------------- /apps/client/public/fonts/PangMenZhengDaoBiaoTiTi.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/fonts/PangMenZhengDaoBiaoTiTi.woff2 -------------------------------------------------------------------------------- /apps/client/public/fonts/font_2533226_rc5qpwobx9c.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/funblog/HEAD/apps/client/public/fonts/font_2533226_rc5qpwobx9c.woff2 -------------------------------------------------------------------------------- /apps/client/src/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from './Image'; 2 | import NormalImage from './NormalImage'; 3 | 4 | export { Image, NormalImage }; 5 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostCard/index.tsx: -------------------------------------------------------------------------------- 1 | import ColCard from './ColCard'; 2 | import RowCard from './RowCard'; 3 | 4 | export { ColCard, RowCard }; 5 | -------------------------------------------------------------------------------- /apps/server/src/config/configuration.ts: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | jwt: { 3 | secret: process.env.JWT_SECRET, 4 | expiresIn: 3, 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/setting/user/style.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | :global { 3 | .ant-form-item-label { 4 | width: 180px; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/components/Action/index.ts: -------------------------------------------------------------------------------- 1 | export { Action } from './Action'; 2 | export type { Props as ActionProps } from './Action'; 3 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/components/TreeItem/index.ts: -------------------------------------------------------------------------------- 1 | export { TreeItem } from './TreeItem'; 2 | export { SortableTreeItem } from './SortableTreeItem'; 3 | -------------------------------------------------------------------------------- /apps/client/src/app/apis/health-check/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET() { 2 | return Response.json({ 3 | code: 0, 4 | message: 'success', 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/blogLike.ts: -------------------------------------------------------------------------------- 1 | export interface BlogLike { 2 | _id: string; 3 | userId: string; 4 | ip: string; 5 | articleId: string; 6 | } 7 | -------------------------------------------------------------------------------- /apps/admin/src/middlewares/types.ts: -------------------------------------------------------------------------------- 1 | import { NextMiddleware } from 'next/server'; 2 | 3 | export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware; 4 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/post/[id]/constants.ts: -------------------------------------------------------------------------------- 1 | export const MARKDOWN_CONTENT_ID = 'post-markdown-content'; 2 | export const COMMENT_CLASS_NAME = 'post-comment-container'; 3 | -------------------------------------------------------------------------------- /apps/client/src/middlewares/types.ts: -------------------------------------------------------------------------------- 1 | import { NextMiddleware } from 'next/server'; 2 | 3 | export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware; 4 | -------------------------------------------------------------------------------- /apps/server/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /packages/types/rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // input: './index.ts', 3 | // output: { 4 | // file: 'index.js', 5 | // format: 'cjs', 6 | // }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/role-permission/role/types.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '@funblog/types'; 2 | 3 | export interface IRole extends Role { 4 | permissions: number[]; 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/role-permission/user/types.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '@funblog/types'; 2 | 3 | export interface IRole extends Role { 4 | permissions: number[]; 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Action } from './Action'; 2 | export { Handle } from './Handle'; 3 | export { Remove } from './Remove'; 4 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/blogComment.ts: -------------------------------------------------------------------------------- 1 | import { Comment } from './comment'; 2 | 3 | export interface BlogComment extends Comment { 4 | articleId: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/components/src/styles/index.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 1em; 3 | height: 1em; 4 | vertical-align: -0.15em; 5 | fill: currentColor; 6 | overflow: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Bubble/types.ts: -------------------------------------------------------------------------------- 1 | export enum Direction { 2 | TOP = 'top', 3 | BOTTOM = 'bottom', 4 | LEFT = 'left', 5 | RIGHT = 'right', 6 | } 7 | -------------------------------------------------------------------------------- /apps/client/src/styles/components/line.css: -------------------------------------------------------------------------------- 1 | @layer components { 2 | .line-tick { 3 | @apply absolute bottom-[-2px] left-0 h-[3px] w-full rounded-full bg-primary; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/client/src/styles/utilities/index.css: -------------------------------------------------------------------------------- 1 | @import './safe.css'; 2 | @import './shadow.css'; 3 | @import './transition.css'; 4 | @import './background.css'; 5 | @import './mask.css'; 6 | -------------------------------------------------------------------------------- /apps/server/README.md: -------------------------------------------------------------------------------- 1 | ### 语法生成 2 | 3 | `nest g resource test --no-spec` 生成 module 模块 4 | 5 | 6 | 7 | 8 | 1. 兼容webp格式 https://lewinblog.com/blog/page/2022/221008-webp-compatibility.md -------------------------------------------------------------------------------- /apps/admin/src/components/ImageList/style.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | :global { 3 | .ant-pro-card-body { 4 | padding-left: 0; 5 | padding-right: 10px; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/hooks/types/src/useOutsideClick.d.ts: -------------------------------------------------------------------------------- 1 | export default function useOutsideClick(target: Element | (() => Element | null), callback: (event: T) => void): void; 2 | -------------------------------------------------------------------------------- /packages/hooks/types/useOutsideClick.d.ts: -------------------------------------------------------------------------------- 1 | export default function useOutsideClick(target: Element | (() => Element | null), callback: (event: T) => void): void; 2 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "React Library", 3 | "extends": "./library.config.json", 4 | "compilerOptions": { 5 | "jsx": "react" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import PostPage from '@/proComponents/PostPage'; 2 | export default function Main() { 3 | return ; 4 | } 5 | 6 | export const revalidate = 0; 7 | -------------------------------------------------------------------------------- /apps/server/src/utils/ip.ts: -------------------------------------------------------------------------------- 1 | import IP2Region from 'ip2region'; 2 | const query = new IP2Region(); 3 | 4 | export const getLocationByIp = (ip: string) => { 5 | return query.search(ip); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/types/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/eslint-config/common-ts"], 3 | "rules": { 4 | "no-use-before-define": "off" 5 | }, 6 | "ignorePatterns": ["index.js"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/types/bindClickOutSide.d.ts: -------------------------------------------------------------------------------- 1 | export default function bindClickOutSide(target: Element | (() => Element | null), callback: (event: T) => void): () => void; 2 | -------------------------------------------------------------------------------- /apps/server/src/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { PUBLIC_KEY } from 'src/constants'; 3 | 4 | export const Public = () => SetMetadata(PUBLIC_KEY, true); 5 | -------------------------------------------------------------------------------- /packages/hooks/types/useSize.d.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from 'react'; 2 | export default function useSize(ref: RefObject): { 3 | width: number; 4 | height: number; 5 | }; 6 | -------------------------------------------------------------------------------- /apps/client/src/api/svg.ts: -------------------------------------------------------------------------------- 1 | import { Svg } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function getSvgList() { 5 | return request.get('/api/p/svg/list'); 6 | } 7 | -------------------------------------------------------------------------------- /apps/client/src/api/tag.ts: -------------------------------------------------------------------------------- 1 | import { Tag } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function getTagList() { 5 | return request.get('/api/p/tag/list'); 6 | } 7 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/post/[id]/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import NotFound from '@/components/NotFound'; 3 | 4 | function Error() { 5 | return ; 6 | } 7 | 8 | export default Error; 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/userSystem.ts: -------------------------------------------------------------------------------- 1 | export interface UserSystem { 2 | _id: string; 3 | username: string; 4 | roles: string[]; 5 | password?: string; 6 | github?: string; 7 | } 8 | -------------------------------------------------------------------------------- /packages/hooks/types/src/useSize.d.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from 'react'; 2 | export default function useSize(ref: RefObject): { 3 | width: number; 4 | height: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/utils/src/randomInteger.ts: -------------------------------------------------------------------------------- 1 | function randomInteger(min: number, max: number): number { 2 | return Math.floor(Math.random() * (max - min + 1)) + min; 3 | } 4 | 5 | export default randomInteger; 6 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/NavigationSidebar/contants.ts: -------------------------------------------------------------------------------- 1 | export const TITLE_HOT = '大家喜欢'; 2 | export const TITLE_LATEST = '最近更新'; 3 | export const TITLE_TAG = 'My Tags'; 4 | export const TITLE_USER = '关注我'; 5 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/blogTag.ts: -------------------------------------------------------------------------------- 1 | export interface BlogTag { 2 | _id: string; 3 | name: string; 4 | icon: string; 5 | color: string; 6 | isVisible: boolean; 7 | seq?: number; 8 | } 9 | -------------------------------------------------------------------------------- /packages/types/lib/esm/svg.js: -------------------------------------------------------------------------------- 1 | var SvgScope; 2 | (function (SvgScope) { 3 | SvgScope["CLIENT"] = "client"; 4 | SvgScope["ADMIN"] = "admin"; 5 | })(SvgScope || (SvgScope = {})); 6 | 7 | export { SvgScope }; 8 | -------------------------------------------------------------------------------- /packages/utils/src/noopPromise.ts: -------------------------------------------------------------------------------- 1 | export default function noopPromiseArray() { 2 | return new Promise((resolve) => { 3 | // 这里什么都不做,直接返回一个已完成的空 Promise 4 | resolve([]); 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /apps/client/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const links = [ 2 | { name: 'Github', icon: 'github', url: 'https://github.com/cd-dongzi' }, 3 | { name: '友情链接', icon: 'pengyou', url: 'https://dzblog.cn/links' }, 4 | ]; 5 | -------------------------------------------------------------------------------- /packages/hooks/types/src/useResize.d.ts: -------------------------------------------------------------------------------- 1 | export default function useResize({ debounce, delay, callback, }: { 2 | debounce?: boolean; 3 | delay?: number; 4 | callback: (e?: UIEvent) => void; 5 | }): void; 6 | -------------------------------------------------------------------------------- /packages/hooks/types/useResize.d.ts: -------------------------------------------------------------------------------- 1 | export default function useResize({ debounce, delay, callback, }: { 2 | debounce?: boolean; 3 | delay?: number; 4 | callback: (e?: UIEvent) => void; 5 | }): void; 6 | -------------------------------------------------------------------------------- /packages/prettier-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['prettier-plugin-tailwindcss'], 3 | semi: true, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | useTabs: false, 7 | printWidth: 120, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/utils/src/canUseDom.ts: -------------------------------------------------------------------------------- 1 | // 是否能使用dom 2 | const canUseDom = () => { 3 | return !!(typeof window !== 'undefined' && window.document && window.document.createElement); 4 | }; 5 | export default canUseDom; 6 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | 3 | export default async function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormItem/style.module.css: -------------------------------------------------------------------------------- 1 | .item { 2 | width: 100% !important; 3 | } 4 | 5 | .proList { 6 | :global { 7 | .ant-pro-table-alert { 8 | display: none; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/index.tsx: -------------------------------------------------------------------------------- 1 | import Banner from './Banner'; 2 | import CoverBanner from './CoverBanner'; 3 | import SwiperBanner from './SwiperBanner'; 4 | 5 | export { Banner, CoverBanner, SwiperBanner }; 6 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostCatalog/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { usePostCatalog } from './usePostCatalog'; 2 | import { useSidebarScroll } from './useSidebarScroll'; 3 | 4 | export { usePostCatalog, useSidebarScroll }; 5 | -------------------------------------------------------------------------------- /apps/server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/visitor.ts: -------------------------------------------------------------------------------- 1 | export interface Visitor { 2 | userAgent: string; 3 | ip: string; 4 | system: Record; 5 | location: Record; 6 | createTime: Date; 7 | } 8 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@funblog/tsconfig", 3 | "version": "2.0.7", 4 | "license": "MIT", 5 | "dependencies": { 6 | "tslib": "^2.6.2", 7 | "typescript": "^5.2.2" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scripts/prisma-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env.default 3 | 4 | cd apps/server 5 | 6 | pnpm run prisma:init 7 | 8 | MYSQL_DATABASE_URL=$MYSQL_DATABASE_URL pnpm run prisma:migrate --name update 9 | 10 | cd ../../ -------------------------------------------------------------------------------- /apps/admin/src/components/Editor/style.module.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | :global { 3 | .emoji-container { 4 | position: absolute; 5 | top: 32px; 6 | left: 0; 7 | z-index: 999; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /apps/server/src/utils/ua.ts: -------------------------------------------------------------------------------- 1 | import Parser from 'ua-parser-js'; 2 | 3 | // 解析ua数据 4 | export const getDataByUa = (ua?: string) => { 5 | if (!ua) return null; 6 | const vm = new Parser(ua); 7 | return vm.getResult(); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/utils/src/isPc.ts: -------------------------------------------------------------------------------- 1 | export default function isPc(ua: string) { 2 | if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) { 3 | return false; 4 | } else { 5 | return true; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/NavigationSidebar/PC/index.ts: -------------------------------------------------------------------------------- 1 | import NavigationSidebar from './main'; 2 | import NavigationSidebarWithContext from './withContext'; 3 | 4 | export { NavigationSidebar, NavigationSidebarWithContext }; 5 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/svg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.SvgScope = void 0; 4 | (function (SvgScope) { 5 | SvgScope["CLIENT"] = "client"; 6 | SvgScope["ADMIN"] = "admin"; 7 | })(exports.SvgScope || (exports.SvgScope = {})); 8 | -------------------------------------------------------------------------------- /packages/utils/types/formatDate.d.ts: -------------------------------------------------------------------------------- 1 | type DateFormat = 'YYYY-MM-DD' | 'YYYY-MM-DD HH:mm:ss' | 'M月D日 · YYYY年' | string; 2 | declare function formatDate(time: string | number, format?: DateFormat): string; 3 | export default formatDate; 4 | -------------------------------------------------------------------------------- /apps/admin/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'postcss-mixins': {}, 5 | 'tailwindcss/nesting': {}, 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /apps/client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'postcss-mixins': {}, 5 | 'tailwindcss/nesting': {}, 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /apps/client/src/styles/base/size.css: -------------------------------------------------------------------------------- 1 | @layer base { 2 | :root { 3 | --padding-page: theme(spacing.4); 4 | } 5 | @media screen(lg) { 6 | :root { 7 | --padding-page: theme(spacing.10); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/aboutme/Me/style.module.css: -------------------------------------------------------------------------------- 1 | 2 | .snippet { 3 | font-size: 14px; 4 | line-height: 1.5; 5 | 6 | &::first-letter { 7 | font-size: 18px; 8 | font-weight: 500; 9 | margin-right: 4px; 10 | } 11 | } -------------------------------------------------------------------------------- /apps/server/src/modules/tag/prisma/tag.prisma: -------------------------------------------------------------------------------- 1 | import { Post } from "../../post/prisma/post" 2 | 3 | model Tag { 4 | id Int @id @default(autoincrement()) 5 | name String @unique 6 | alias String @unique 7 | posts Post[] 8 | } 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/visitor/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsOptional } from 'class-validator'; 2 | 3 | export class CreateVisitorDto { 4 | @IsOptional() 5 | ip?: string; 6 | 7 | @IsOptional() 8 | userAgent?: string; 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/types/components/SvgBox/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | declare function SvgBox({ className, content }: { 3 | className?: string; 4 | content: string; 5 | }): React.JSX.Element; 6 | export default SvgBox; 7 | -------------------------------------------------------------------------------- /packages/hooks/types/usePrevious.d.ts: -------------------------------------------------------------------------------- 1 | export type ShouldUpdateFunc = (prev: T | undefined, next: T) => boolean; 2 | declare function usePrevious(state: T, shouldUpdate?: ShouldUpdateFunc): T | undefined; 3 | export default usePrevious; 4 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/library.config.json"], 3 | "compilerOptions": { 4 | "declarationDir": "./types", 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": ["node_modules", "./types"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/utils/types/getOS.d.ts: -------------------------------------------------------------------------------- 1 | export default function getOS(ua: string): { 2 | isChrome: boolean; 3 | isFireFox: boolean; 4 | isTablet: boolean; 5 | isPhone: boolean; 6 | isAndroid: boolean; 7 | isPc: boolean; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/client/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post'; 2 | export * from './tag'; 3 | export * from './site'; 4 | export * from './user'; 5 | export * from './page'; 6 | export * from './visitor'; 7 | export * from './link'; 8 | export * from './svg'; 9 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/page/[page]/page.tsx: -------------------------------------------------------------------------------- 1 | import PostPage from '@/proComponents/PostPage'; 2 | 3 | function Page({ params }: { params: { page: string } }) { 4 | return ; 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /packages/constants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/library.config.json"], 3 | "compilerOptions": { 4 | "declarationDir": "./types", 5 | }, 6 | "include": ["*/*.ts"], 7 | "exclude": ["node_modules", "./types"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/hooks/types/src/usePrevious.d.ts: -------------------------------------------------------------------------------- 1 | export type ShouldUpdateFunc = (prev: T | undefined, next: T) => boolean; 2 | declare function usePrevious(state: T, shouldUpdate?: ShouldUpdateFunc): T | undefined; 3 | export default usePrevious; 4 | -------------------------------------------------------------------------------- /packages/utils/src/delay.ts: -------------------------------------------------------------------------------- 1 | export default function delay(time: number) { 2 | return new Promise((resolve) => { 3 | const timer = setTimeout(() => { 4 | clearTimeout(timer); 5 | resolve(); 6 | }, time); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/http/http.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HttpService } from './http.service'; 3 | 4 | @Module({ 5 | providers: [HttpService], 6 | exports: [HttpService], 7 | }) 8 | export class HttpModule {} 9 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const czConfig = require('./.cz-config'); 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', czConfig.types.map((item) => item.value)], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | ## 执行启动命令 2 | 3 | `docker compose --env-file ../.env.default up -d` 4 | 5 | 6 | 1. 进入`http://localhost:8003/register` 基于.env.default中的邀请码进行账号注册 7 | 2. 登录之后进入`http://localhost:8003/init` 进行数据初始化 8 | 3. 访问 `http://localhost:8002` 便可展示初始数据 -------------------------------------------------------------------------------- /packages/types/lib/esm/index.js: -------------------------------------------------------------------------------- 1 | export { CommentStatus, CommentType } from './comment.js'; 2 | export { PageMenuButtonType, PageMenuType } from './page.js'; 3 | export { LinkStatus, LinkType } from './link.js'; 4 | export { SvgScope } from './svg.js'; 5 | -------------------------------------------------------------------------------- /apps/server/src/modules/category/prisma/category.prisma: -------------------------------------------------------------------------------- 1 | import { Post } from "../../post/prisma/post" 2 | 3 | model Category { 4 | id Int @id @default(autoincrement()) 5 | name String @unique 6 | alias String @unique 7 | posts Post[] 8 | } 9 | -------------------------------------------------------------------------------- /packages/types/src/svg.ts: -------------------------------------------------------------------------------- 1 | export enum SvgScope { 2 | CLIENT = 'client', 3 | ADMIN = 'admin', 4 | } 5 | export interface Svg { 6 | id: number; 7 | content: string; 8 | name: string; 9 | scope?: SvgScope[]; 10 | desc?: string; 11 | } 12 | -------------------------------------------------------------------------------- /scripts/git.js: -------------------------------------------------------------------------------- 1 | const git = require('simple-git'); 2 | 3 | async function getModifiedFiles() { 4 | const res = await git(process.cwd()).status(); 5 | return [...res.modified, ...res.created]; 6 | } 7 | 8 | exports.getModifiedFiles = getModifiedFiles; 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/prisma/config.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | output = "../node_modules/.prisma/client" 4 | } 5 | 6 | datasource db { 7 | provider = "mysql" 8 | url = env("MYSQL_DATABASE_URL") 9 | } 10 | -------------------------------------------------------------------------------- /apps/admin/src/styles/utilities.css: -------------------------------------------------------------------------------- 1 | @layer utilities { 2 | .transition-all-5 { 3 | transition: all 0.5s; 4 | } 5 | .transition-all-3 { 6 | transition: all 0.3s; 7 | } 8 | .transition-color-3 { 9 | transition: color 0.3s; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/client/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { composeMiddlewares } from './middlewares/composeMiddlewares'; 2 | import { proxyMiddleware } from './middlewares/proxyMiddleware'; 3 | 4 | const middlewares = [proxyMiddleware]; 5 | export default composeMiddlewares(middlewares); 6 | -------------------------------------------------------------------------------- /packages/hooks/rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | external: ['react', /^core-js/, /^@funblog\/utils(?:.+)?$/], 3 | importLibrary: [ 4 | { 5 | libraryName: '@funblog/utils', 6 | libraryDirectory: 'lib/cjs', 7 | }, 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spin } from 'antd'; 2 | 3 | function PageLoading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default PageLoading; 12 | -------------------------------------------------------------------------------- /apps/admin/src/components/DndList/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as DndList } from './DndList'; 2 | export type { DndListProps } from './DndList'; 3 | export type { TreeItem, TreeItems, FlattenedItem } from './types'; 4 | export { buildTree as unFlattenItems } from './utils'; 5 | -------------------------------------------------------------------------------- /apps/admin/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const PAGE_WHITELIST = ['/login', '/register', '/restore']; 2 | 3 | export const PAGE_CONTENT_CLASSNAME = 'page-content'; 4 | 5 | export const PAGE_HEADER_EXTRA = 'page-header-extra'; 6 | 7 | export const MEDIA_PAGE_SIZE = 20; 8 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostCatalog/index.ts: -------------------------------------------------------------------------------- 1 | import { usePostCatalog, useSidebarScroll } from './hooks'; 2 | import { PostCatalog, ChangeEventData } from './main'; 3 | 4 | export { PostCatalog, usePostCatalog, useSidebarScroll }; 5 | export type { ChangeEventData }; 6 | -------------------------------------------------------------------------------- /packages/types/src/invitationCode.ts: -------------------------------------------------------------------------------- 1 | import { Role } from './rolePermission'; 2 | 3 | export interface InvitationCode { 4 | id: number; 5 | code: string; 6 | createdAt: string; 7 | updatedAt: string; 8 | expiredAt: string; 9 | roles: Role[]; 10 | } 11 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/setting/layout.tsx: -------------------------------------------------------------------------------- 1 | import Main from '@/components/Main'; 2 | 3 | function SettingLayout({ children }: { children: React.ReactNode }) { 4 | return
{children}
; 5 | } 6 | 7 | export default SettingLayout; 8 | -------------------------------------------------------------------------------- /apps/admin/src/components/Editor/plugins/rawHTML.tsx: -------------------------------------------------------------------------------- 1 | import { BytemdPlugin } from 'bytemd'; 2 | import rehypeRaw from 'rehype-raw'; 3 | export default function rawHTML(): BytemdPlugin { 4 | return { 5 | rehype: (processor) => processor.use(rehypeRaw as any), 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /apps/server/src/modules/svg/prisma/svg.prisma: -------------------------------------------------------------------------------- 1 | model Svg { 2 | id Int @id @default(autoincrement()) 3 | name String @unique 4 | content String @db.LongText 5 | scope Json? 6 | desc String? 7 | createdAt DateTime @default(now()) 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | import Icon from './components/Icon'; 2 | import Portal from './components/Portal'; 3 | import SvgBox from './components/SvgBox'; 4 | import SvgContainer from './components/SvgContainer'; 5 | 6 | export { Icon, Portal, SvgBox, SvgContainer }; 7 | -------------------------------------------------------------------------------- /packages/components/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import Icon from './components/Icon'; 2 | import Portal from './components/Portal'; 3 | import SvgBox from './components/SvgBox'; 4 | import SvgContainer from './components/SvgContainer'; 5 | export { Icon, Portal, SvgBox, SvgContainer }; 6 | -------------------------------------------------------------------------------- /apps/client/src/styles/utilities/transition.css: -------------------------------------------------------------------------------- 1 | @layer utilities { 2 | .transition-all-5 { 3 | transition: all 0.5s; 4 | } 5 | .transition-all-3 { 6 | transition: all 0.3s; 7 | } 8 | .transition-color-3 { 9 | transition: color 0.3s; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/types/types/invitationCode.d.ts: -------------------------------------------------------------------------------- 1 | import { Role } from './rolePermission'; 2 | export interface InvitationCode { 3 | id: number; 4 | code: string; 5 | createdAt: string; 6 | updatedAt: string; 7 | expiredAt: string; 8 | roles: Role[]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/types/types/svg.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum SvgScope { 2 | CLIENT = "client", 3 | ADMIN = "admin" 4 | } 5 | export interface Svg { 6 | id: number; 7 | content: string; 8 | name: string; 9 | scope?: SvgScope[]; 10 | desc?: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/client/src/context/global/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { GlobalContext } from './context'; 4 | import { GlobalProvider } from './provider'; 5 | 6 | export const useStore = () => useContext(GlobalContext); 7 | export { GlobalContext, GlobalProvider }; 8 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/components/CommentBox/style.module.css: -------------------------------------------------------------------------------- 1 | .item { 2 | &.hide { 3 | .time { 4 | &:hover { 5 | transform: translateX(0) !important; 6 | } 7 | } 8 | .reply { 9 | display: none; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostCatalog/types.ts: -------------------------------------------------------------------------------- 1 | export interface Catalog { 2 | node: Element; 3 | layer: number; 4 | text: string; 5 | } 6 | export interface PostCatalogItem { 7 | title: string; 8 | gap: number; 9 | node: Element; 10 | selected?: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/react-library.config.json"], 3 | "compilerOptions": { 4 | "declarationDir": "./types" 5 | }, 6 | "include": ["src/**/*.ts", "src/**/*.tsx", "global.d.ts"], 7 | "exclude": ["node_modules", "./types"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/tsconfig/library.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "Library", 3 | "extends": "./base.config.json", 4 | "compilerOptions": { 5 | "target": "es5", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "allowJs": true, 8 | "module": "esnext" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/GlobalData/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import GetData from './GetData'; 3 | 4 | function GlobalData() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default GlobalData; 13 | -------------------------------------------------------------------------------- /apps/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/server.config.json"], 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "esModuleInterop": true, 7 | "jsx": "react" 8 | }, 9 | "exclude": ["node_modules", "dist"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/messages/page.tsx: -------------------------------------------------------------------------------- 1 | import { CommentType } from '@funblog/types'; 2 | import Comment from '@/proComponents/Comment'; 3 | 4 | async function Messages() { 5 | return ; 6 | } 7 | 8 | export default Messages; 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { PrismaService } from './prisma.service'; 3 | 4 | @Global() 5 | @Module({ 6 | providers: [PrismaService], 7 | exports: [PrismaService], 8 | }) 9 | export class PrismaModule {} 10 | -------------------------------------------------------------------------------- /packages/hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/library.config.json"], 3 | "compilerOptions": { 4 | "outDir": "./types", 5 | "declarationDir": "./types", 6 | }, 7 | "include": ["**/*.ts", "**/*.tsx"], 8 | "exclude": ["node_modules", "./types"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/library.config.json"], 3 | "compilerOptions": { 4 | "outDir": "./types", 5 | "declarationDir": "./types", 6 | }, 7 | "include": ["**/*.ts", "**/*.tsx"], 8 | "exclude": ["node_modules", "./types"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/admin/src/context/helper/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export interface HelperContextProps { 4 | serverUrl?: string; 5 | } 6 | export interface HelperContextData extends HelperContextProps {} 7 | export const HelperContext = createContext({} as HelperContextData); 8 | -------------------------------------------------------------------------------- /apps/client/src/components/Tooltip/style.module.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | padding: 8px 10px; 3 | border-radius: 8px; 4 | min-height: 16px; 5 | line-height: 16px; 6 | max-width: 244px; 7 | font-size: 14px; 8 | background-color: theme(colors.white); 9 | color: theme(colors.text); 10 | } 11 | -------------------------------------------------------------------------------- /apps/client/src/context/helper/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export interface HelperContextProps { 4 | serverUrl?: string; 5 | } 6 | export interface HelperContextData extends HelperContextProps {} 7 | export const HelperContext = createContext({} as HelperContextData); 8 | -------------------------------------------------------------------------------- /apps/client/src/context/page/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Context as PageContext } from './context'; 4 | import { Provider as PageProvider } from './provider'; 5 | 6 | export const usePageStore = () => useContext(PageContext); 7 | export { PageContext, PageProvider }; 8 | -------------------------------------------------------------------------------- /packages/tsconfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@funblog/tsconfig/react-library.config.json"], 3 | "compilerOptions": { 4 | "outDir": "./types", 5 | "declarationDir": "./types" 6 | }, 7 | "include": ["**/*.ts", "**/*.tsx"], 8 | "exclude": ["node_modules", "./types"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/types/src/image.ts: -------------------------------------------------------------------------------- 1 | export interface Image { 2 | id: number; 3 | originalname: string; 4 | filename: string; 5 | mimetype: string; 6 | url: string; 7 | originalUrl?: string; 8 | thumbnailUrl?: string; 9 | metadata: Record; 10 | createdAt: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/globalConfig/globalConfig.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { GlobalConfigService } from './globalConfig.service'; 3 | 4 | @Module({ 5 | providers: [GlobalConfigService], 6 | exports: [GlobalConfigService], 7 | }) 8 | export class GlobalConfigModule {} 9 | -------------------------------------------------------------------------------- /packages/types/src/visitor.ts: -------------------------------------------------------------------------------- 1 | export interface Visitor { 2 | id: number; 3 | ip: string; 4 | userAgent?: string; 5 | country?: string; 6 | province?: string; 7 | city?: string; 8 | isp?: string; 9 | system?: Record; 10 | createdAt: Date; 11 | updatedAt: Date; 12 | } 13 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import Indicators from './Indicators'; 2 | import StatisticList from './StatisticList'; 3 | function Dashboard() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default Dashboard; 13 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostPage/Carousel/index.tsx: -------------------------------------------------------------------------------- 1 | import { Post } from '@funblog/types'; 2 | import { SwiperBanner } from '@/proComponents/Banner'; 3 | 4 | async function Carousel({ postList }: { postList: Post[] }) { 5 | return ; 6 | } 7 | 8 | export default Carousel; 9 | -------------------------------------------------------------------------------- /packages/cli/bin/replace.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('commander'); 2 | const { replaceWorkspace } = require('../lib/replaceWorkspace'); 3 | 4 | const program = new Command('replace'); 5 | program.command('workspace').description('更换workspace版本号').action(replaceWorkspace); 6 | 7 | module.exports = program; 8 | -------------------------------------------------------------------------------- /packages/hooks/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const debounceFn = (func: (...args: any[]) => void, delay = 300) => { 2 | let timer: number; 3 | return function (...args: any[]) { 4 | clearTimeout(timer); 5 | timer = setTimeout(() => { 6 | func(...args); 7 | }, delay); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/login/style.module.css: -------------------------------------------------------------------------------- 1 | .login { 2 | background-image: url(/background.svg); 3 | background-repeat: no-repeat; 4 | background-position: center 110px; 5 | background-size: 100%; 6 | 7 | /* :global(.ant-pro-form-login-top) { 8 | margin-bottom: 40px; 9 | } */ 10 | } 11 | -------------------------------------------------------------------------------- /packages/components/types/components/Portal/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | type Props = { 3 | children: any; 4 | container?: any; 5 | }; 6 | interface PortalProps { 7 | (props: Props): JSX.Element | null; 8 | } 9 | declare const Portal: PortalProps; 10 | export default Portal; 11 | -------------------------------------------------------------------------------- /packages/eslint-config/common-ts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:@typescript-eslint/recommended', './common'], 3 | rules: { 4 | '@typescript-eslint/no-explicit-any': 'off', 5 | '@typescript-eslint/no-unused-vars': 'error', 6 | '@typescript-eslint/no-var-requires': 'error', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/tsconfig/web.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./base.config.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "module": "esnext", 8 | "jsx": "preserve" 9 | }, 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/utils/src/formatDate.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | type DateFormat = 'YYYY-MM-DD' | 'YYYY-MM-DD HH:mm:ss' | 'M月D日 · YYYY年' | string; 4 | 5 | function formatDate(time: string | number, format: DateFormat = 'YYYY-MM-DD') { 6 | return dayjs(time).format(format); 7 | } 8 | export default formatDate; 9 | -------------------------------------------------------------------------------- /apps/admin/src/components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import { LayoutProps } from './_layout'; 3 | 4 | const NoSSR = dynamic(() => import('./_layout'), { ssr: false }); 5 | function Layout(props: LayoutProps) { 6 | return ; 7 | } 8 | 9 | export default Layout; 10 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/NavigationSidebar/index.ts: -------------------------------------------------------------------------------- 1 | import MNavigationSidebar, { ChildrenParams } from './Mobile'; 2 | import { NavigationSidebar, NavigationSidebarWithContext } from './PC'; 3 | 4 | export { MNavigationSidebar, NavigationSidebar, NavigationSidebarWithContext }; 5 | export type { ChildrenParams }; 6 | -------------------------------------------------------------------------------- /packages/types/types/image.d.ts: -------------------------------------------------------------------------------- 1 | export interface Image { 2 | id: number; 3 | originalname: string; 4 | filename: string; 5 | mimetype: string; 6 | url: string; 7 | originalUrl?: string; 8 | thumbnailUrl?: string; 9 | metadata: Record; 10 | createdAt: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/admin/src/components/Image/NormalImage.tsx: -------------------------------------------------------------------------------- 1 | function NormalImage(props: React.DetailedHTMLProps, HTMLImageElement>) { 2 | // eslint-disable-next-line @next/next/no-img-element 3 | return ; 4 | } 5 | 6 | export default NormalImage; 7 | -------------------------------------------------------------------------------- /apps/client/src/components/Image/NormalImage.tsx: -------------------------------------------------------------------------------- 1 | function NormalImage(props: React.DetailedHTMLProps, HTMLImageElement>) { 2 | // eslint-disable-next-line @next/next/no-img-element 3 | return ; 4 | } 5 | 6 | export default NormalImage; 7 | -------------------------------------------------------------------------------- /apps/client/src/styles/utilities/mask.css: -------------------------------------------------------------------------------- 1 | @layer utilities { 2 | .mask-scroll { 3 | mask: 4 | linear-gradient(transparent, theme(colors.black) 1rem, theme(colors.black) calc(100% - 1rem), transparent), 5 | linear-gradient(theme(colors.black), theme(colors.black)) no-repeat right top/12px 100%; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/server/src/modules/role/role.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RoleController } from './role.controller'; 3 | import { RoleService } from './role.service'; 4 | 5 | @Module({ 6 | controllers: [RoleController], 7 | providers: [RoleService], 8 | }) 9 | export class RoleModule {} 10 | -------------------------------------------------------------------------------- /packages/hooks/types/useDisabledScrollByMask.d.ts: -------------------------------------------------------------------------------- 1 | export type Options = { 2 | show: boolean; 3 | disabledScroll?: boolean; 4 | maskEl?: HTMLElement | null; 5 | contentEl?: HTMLElement | null; 6 | }; 7 | export default function useDisabledScrollByMask({ show, disabledScroll, maskEl, contentEl }?: Options): void; 8 | -------------------------------------------------------------------------------- /packages/hooks/types/useScroll.d.ts: -------------------------------------------------------------------------------- 1 | export interface ScrollProps { 2 | debounce?: boolean; 3 | delay?: number; 4 | callback: (e?: Event) => void; 5 | getContainer?: () => HTMLElement | null | undefined; 6 | } 7 | export default function useScroll({ debounce, delay, callback, getContainer }: ScrollProps): void; 8 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/loading.tsx: -------------------------------------------------------------------------------- 1 | import Loading from '@/components/Loading'; 2 | function PageLoading() { 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | 10 | export default PageLoading; 11 | -------------------------------------------------------------------------------- /packages/hooks/types/src/useScroll.d.ts: -------------------------------------------------------------------------------- 1 | export interface ScrollProps { 2 | debounce?: boolean; 3 | delay?: number; 4 | callback: (e?: Event) => void; 5 | getContainer?: () => HTMLElement | null | undefined; 6 | } 7 | export default function useScroll({ debounce, delay, callback, getContainer }: ScrollProps): void; 8 | -------------------------------------------------------------------------------- /packages/types/types/visitor.d.ts: -------------------------------------------------------------------------------- 1 | export interface Visitor { 2 | id: number; 3 | ip: string; 4 | userAgent?: string; 5 | country?: string; 6 | province?: string; 7 | city?: string; 8 | isp?: string; 9 | system?: Record; 10 | createdAt: Date; 11 | updatedAt: Date; 12 | } 13 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/messages/loading.tsx: -------------------------------------------------------------------------------- 1 | import Loading from '@/components/Loading'; 2 | 3 | function Loading1() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default Loading1; 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/image/image.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ImageController } from './image.controller'; 3 | import { ImageService } from './image.service'; 4 | 5 | @Module({ 6 | controllers: [ImageController], 7 | providers: [ImageService], 8 | }) 9 | export class ImageModule {} 10 | -------------------------------------------------------------------------------- /apps/client/src/components/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles.module.css'; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 | ); 12 | }; 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /packages/components/types/components/SvgContainer/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | interface SvgContainerProps { 3 | list: { 4 | name: string; 5 | content: string; 6 | }[]; 7 | } 8 | declare function SvgContainer({ list }: SvgContainerProps): React.JSX.Element | null; 9 | export default SvgContainer; 10 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/GlobalData.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { GlobalProvider, GlobalContextProps } from '@/context'; 3 | 4 | function GlobalData({ children, ...props }: { children: React.ReactNode } & Partial) { 5 | return {children}; 6 | } 7 | 8 | export default GlobalData; 9 | -------------------------------------------------------------------------------- /apps/admin/src/context/global/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { GlobalContext, GlobalContextProps } from './context'; 4 | import { GlobalProvider } from './provider'; 5 | 6 | export const useStore = () => useContext(GlobalContext); 7 | export { GlobalContext, GlobalProvider }; 8 | export type { GlobalContextProps }; 9 | -------------------------------------------------------------------------------- /apps/client/src/app/(other)/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Next.js', 3 | description: 'Generated by Next.js', 4 | }; 5 | 6 | export default function RootLayout({ children }: { children: React.ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/admin/src/context/helper/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { HelperContext, HelperContextProps } from './context'; 4 | import { HelperProvider } from './provider'; 5 | 6 | export const useHelperStore = () => useContext(HelperContext); 7 | export { HelperContext, HelperProvider }; 8 | export type { HelperContextProps }; 9 | -------------------------------------------------------------------------------- /apps/admin/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from './middlewares/authMiddleware'; 2 | import { composeMiddlewares } from './middlewares/composeMiddlewares'; 3 | import { proxyMiddleware } from './middlewares/proxyMiddleware'; 4 | 5 | const middlewares = [proxyMiddleware, authMiddleware]; 6 | export default composeMiddlewares(middlewares); 7 | -------------------------------------------------------------------------------- /apps/client/src/components/PreviewImage/style.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | max-height: 100%; 4 | overflow: auto; 5 | padding: 40px 0; 6 | 7 | .img { 8 | max-width: 90%; 9 | width: auto; 10 | height: auto; 11 | object-fit: cover; 12 | display: block; 13 | margin: 40px auto; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/client/src/context/helper/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { HelperContext, HelperContextProps } from './context'; 4 | import { HelperProvider } from './provider'; 5 | 6 | export const useHelperStore = () => useContext(HelperContext); 7 | export { HelperContext, HelperProvider }; 8 | export type { HelperContextProps }; 9 | -------------------------------------------------------------------------------- /apps/server/src/guards/localAuth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class LocalAuthGuard extends AuthGuard('local') { 6 | canActivate(context: ExecutionContext) { 7 | return super.canActivate(context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/mongodb.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongodbController } from './mongodb.controller'; 3 | import { MongodbService } from './mongodb.service'; 4 | 5 | @Module({ 6 | controllers: [MongodbController], 7 | providers: [MongodbService], 8 | }) 9 | export class MongodbModule {} 10 | -------------------------------------------------------------------------------- /packages/utils/src/filterXSS.ts: -------------------------------------------------------------------------------- 1 | import xss, { escapeAttrValue } from 'xss'; 2 | 3 | export default function filterXSS(content: string) { 4 | return xss(content, { 5 | onTagAttr: (tag, name, value) => { 6 | if (name === 'style') { 7 | return `${name}="${escapeAttrValue(value)}"`; 8 | } 9 | }, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/page/constants.ts: -------------------------------------------------------------------------------- 1 | import { PageMenuButtonType } from '@funblog/types'; 2 | 3 | export const MENU_BUTTON_TYPE = { 4 | [PageMenuButtonType.PAGE]: '页面', 5 | [PageMenuButtonType.ARTICLE]: '文章', 6 | [PageMenuButtonType.CATEGORY]: '分类', 7 | [PageMenuButtonType.TAG]: '标签', 8 | [PageMenuButtonType.URL]: '自定义链接', 9 | }; 10 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/State.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { CommentProvider, CommentContextProps } from './store'; 3 | 4 | function CommentState({ children, ...props }: { children: React.ReactNode } & Partial) { 5 | return {children}; 6 | } 7 | 8 | export default CommentState; 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/category/category.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CategoryController } from './category.controller'; 3 | import { CategoryService } from './category.service'; 4 | 5 | @Module({ 6 | controllers: [CategoryController], 7 | providers: [CategoryService], 8 | }) 9 | export class CategoryModule {} 10 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | 3 | type Props = { 4 | className?: string; 5 | children: React.ReactNode; 6 | }; 7 | const Card = ({ children, className }: Props) => { 8 | return
{children}
; 9 | }; 10 | 11 | export default Card; 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/email/email.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EmailController } from './email.controller'; 3 | import { EmailService } from './email.service'; 4 | 5 | @Module({ 6 | controllers: [EmailController], 7 | providers: [EmailService], 8 | exports: [EmailService], 9 | }) 10 | export class EmailModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/init/init.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { Public } from 'src/decorators/public'; 3 | import { InitService } from './init.service'; 4 | 5 | @Public() 6 | @Controller('p/init') 7 | export class InitPublicController { 8 | constructor(private readonly initService: InitService) {} 9 | } 10 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/github.ts: -------------------------------------------------------------------------------- 1 | export interface Github { 2 | _id: string; 3 | name: string; 4 | isVisible: boolean; 5 | stargazers_count: number; 6 | forks_count: number; 7 | subscribers_count: number; 8 | description: string; 9 | html_url: string; 10 | created_at: string; 11 | updated_at: string; 12 | seq: number; 13 | } 14 | -------------------------------------------------------------------------------- /packages/utils/types/theme.d.ts: -------------------------------------------------------------------------------- 1 | declare enum Theme { 2 | light = "light", 3 | dark = "dark" 4 | } 5 | declare function getTheme(): Theme; 6 | declare function setTheme(theme: Theme): void; 7 | declare const theme: { 8 | getTheme: typeof getTheme; 9 | setTheme: typeof setTheme; 10 | toggleTheme: () => void; 11 | }; 12 | export default theme; 13 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/store/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { CommentContext, CommentContextProps } from './context'; 4 | import { CommentProvider } from './provider'; 5 | 6 | export const useCommentStore = () => useContext(CommentContext); 7 | export { CommentContext, CommentProvider }; 8 | export type { CommentContextProps }; 9 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | _id: string; 3 | name: string; 4 | email: string; 5 | refId?: string; 6 | url?: string; 7 | avatar?: string; 8 | role: string[]; 9 | ip: string; 10 | userAgent: string; 11 | system: Record; 12 | location: Record; 13 | createTime: Date; 14 | } 15 | -------------------------------------------------------------------------------- /packages/utils/src/getOffsetLeft.ts: -------------------------------------------------------------------------------- 1 | export default function getOffsetLeft(node: HTMLElement | null, container?: HTMLElement | null) { 2 | if (!node) return 0; 3 | let t = node.offsetLeft; 4 | while (node.offsetParent && node.offsetParent !== container) { 5 | node = node.offsetParent as HTMLElement; 6 | t += node.offsetLeft; 7 | } 8 | return t; 9 | } 10 | -------------------------------------------------------------------------------- /packages/utils/src/getOffsetTop.ts: -------------------------------------------------------------------------------- 1 | export default function getOffsetTop(node: HTMLElement | null, container?: HTMLElement | null) { 2 | if (!node) return 0; 3 | let t = node.offsetTop; 4 | while (node.offsetParent && node.offsetParent !== container) { 5 | node = node.offsetParent as HTMLElement; 6 | t += node.offsetTop; 7 | } 8 | return t; 9 | } 10 | -------------------------------------------------------------------------------- /apps/admin/src/components/PostEditor/style.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | .box { 3 | border-right: 1px solid #e1e4e8; 4 | } 5 | :global { 6 | .bytemd { 7 | height: calc(100vh - 60px); 8 | border: 1px solid #e1e4e8; 9 | border-right: 0px solid transparent; 10 | } 11 | .bytemd-fullscreen { 12 | z-index: 999; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /apps/client/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const HEADER_ID = 'header-container'; 2 | export const SIDEBAR_ID = 'sidebar-container'; 3 | export const FOOTER_ID = 'footer-container'; 4 | export const COMMENT_ID = 'comment-container'; 5 | export const NAVIGATION_SLOT_ID = 'navigation-slot'; 6 | 7 | export const POST_PAGESIZE = 12; 8 | export const DISABLED_SUB_ROUTES = ['/messages']; 9 | -------------------------------------------------------------------------------- /apps/client/src/styles/utilities/safe.css: -------------------------------------------------------------------------------- 1 | @layer utilities { 2 | .pb-safe { 3 | padding-bottom: env(safe-area-inset-bottom); 4 | } 5 | .pr-safe { 6 | padding-right: env(safe-area-inset-right); 7 | } 8 | .pl-safe { 9 | padding-left: env(safe-area-inset-left); 10 | } 11 | .pt-safe { 12 | padding-top: env(safe-area-inset-top); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/permission/permission.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PermissionController } from './permission.controller'; 3 | import { PermissionService } from './permission.service'; 4 | 5 | @Module({ 6 | controllers: [PermissionController], 7 | providers: [PermissionService], 8 | }) 9 | export class PermissionModule {} 10 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [["@funblog/*"]], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["admin", "client", "server"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/client/src/api/link.ts: -------------------------------------------------------------------------------- 1 | import { Link, LinkType } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function createLink(body: Partial) { 5 | return request.post('/api/p/link', { 6 | body, 7 | }); 8 | } 9 | export function getListList() { 10 | return request.get<{ type: LinkType; list: Link[] }[]>('/api/p/link/list'); 11 | } 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/link/prisma/link.prisma: -------------------------------------------------------------------------------- 1 | model Link { 2 | id Int @id @default(autoincrement()) 3 | title String 4 | desc String 5 | url String 6 | logo String @db.Text 7 | type String 8 | visible Boolean @default(true) 9 | status String @default("pending") 10 | createdAt DateTime @default(now()) 11 | } 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/wechaty/wechaty.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { WechatyController } from './wechaty.controller'; 3 | import { WechatyService } from './wechaty.service'; 4 | 5 | @Module({ 6 | controllers: [WechatyController], 7 | providers: [WechatyService], 8 | exports: [WechatyService], 9 | }) 10 | export class WechatyModule {} 11 | -------------------------------------------------------------------------------- /packages/components/types/components/Icon/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | type Props = { 3 | name: string; 4 | mode?: 'symbol' | 'fontClass'; 5 | color?: string; 6 | className?: string; 7 | onClick?: () => void; 8 | }; 9 | declare const Icon: React.ForwardRefExoticComponent>; 10 | export default Icon; 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/init/init.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post } from '@nestjs/common'; 2 | import { InitService } from './init.service'; 3 | 4 | @Controller('init') 5 | export class InitController { 6 | constructor(private readonly initService: InitService) {} 7 | 8 | @Post('data') 9 | initData() { 10 | return this.initService.initData(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/utils/types/checkStr.d.ts: -------------------------------------------------------------------------------- 1 | type Type = 'phone' | 'tel' | 'card' | 'pwd' | 'postal' | 'QQ' | 'email' | 'money' | 'URL' | 'date' | 'number' | 'english' | 'chinese' | 'lower' | 'upper' | 'HTML' | 'IPV6' | 'IPV4'; 2 | /** 3 | * 检查字符串 4 | * @param {*} str 5 | * @param {*} type 6 | */ 7 | export default function checkStr(str: string | undefined | null, type: Type): boolean; 8 | export {}; 9 | -------------------------------------------------------------------------------- /apps/client/src/api/visitor.ts: -------------------------------------------------------------------------------- 1 | import { Visitor } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function createVisitor(body: Partial>) { 5 | return request.post('/api/visitor', { 6 | body, 7 | }); 8 | } 9 | 10 | export function getVisitorCount() { 11 | return request.get('/api/p/visitor/count'); 12 | } 13 | -------------------------------------------------------------------------------- /apps/server/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { LoggerService } from './modules/shared/logger.service'; 3 | 4 | @Injectable() 5 | export class AppService { 6 | constructor(private readonly logger: LoggerService) {} 7 | 8 | getHello(): string { 9 | return 'Hello World!'; 10 | } 11 | 12 | send() { 13 | return 'haha'; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/cli/bin/lib.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('commander'); 2 | const { dev, build } = require('../lib/lib'); 3 | 4 | const program = new Command('lib'); 5 | 6 | program.command('dev').option('--css', '编译css').description('常用库的本地开发命令.').action(dev); 7 | 8 | program.command('build').option('--css', '编译css').description('常用库的打包命令.').action(build); 9 | 10 | module.exports = program; 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/svg/svg.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { SvgController } from './svg.controller'; 3 | import { SvgPublicController } from './svg.public.controller'; 4 | import { SvgService } from './svg.service'; 5 | 6 | @Module({ 7 | controllers: [SvgController, SvgPublicController], 8 | providers: [SvgService], 9 | }) 10 | export class SvgModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/tag/tag.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TagController } from './tag.controller'; 3 | import { TagPublicController } from './tag.public.controller'; 4 | import { TagService } from './tag.service'; 5 | 6 | @Module({ 7 | controllers: [TagController, TagPublicController], 8 | providers: [TagService], 9 | }) 10 | export class TagModule {} 11 | -------------------------------------------------------------------------------- /packages/utils/types/cacheLocal.d.ts: -------------------------------------------------------------------------------- 1 | declare function get(key: string): any; 2 | declare function set(key: string, val: any): void; 3 | declare function remove(key: string): void; 4 | declare function clear(): void; 5 | declare const cacheLocal: { 6 | get: typeof get; 7 | set: typeof set; 8 | remove: typeof remove; 9 | clear: typeof clear; 10 | }; 11 | export default cacheLocal; 12 | -------------------------------------------------------------------------------- /apps/admin/src/styles/index.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import './base.css'; 3 | 4 | @import 'tailwindcss/components'; 5 | 6 | @import 'tailwindcss/utilities'; 7 | @import './utilities.css'; 8 | 9 | @import '@funblog/components/lib/cjs/index.css'; 10 | @import './markdown/index.css'; 11 | @import './reset.css'; 12 | 13 | body { 14 | background-color: #f0f2f5; 15 | min-height: 100vh; 16 | } -------------------------------------------------------------------------------- /apps/client/src/proComponents/Header/MIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@funblog/components'; 2 | 3 | function MIcon({ name, onClick }: { name: string; onClick?: () => void }) { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default MIcon; 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/comment.ts: -------------------------------------------------------------------------------- 1 | export interface Comment { 2 | _id: string; 3 | userId: string; 4 | content: string; 5 | replyList: Partial[]; 6 | name: string; 7 | url: string; 8 | avatar: string; 9 | floor: number; 10 | createTime: string; 11 | role: string[]; 12 | questioner: Pick; 13 | } 14 | -------------------------------------------------------------------------------- /packages/utils/types/cacheCookie.d.ts: -------------------------------------------------------------------------------- 1 | declare const cacheCookie: { 2 | get(key: string): string; 3 | set(key: string, value: any, options?: { 4 | day?: number | undefined; 5 | isSetTopDomain?: boolean | undefined; 6 | }): void; 7 | remove(key: string, options?: { 8 | isSetTopDomain?: boolean | undefined; 9 | }): void; 10 | }; 11 | export default cacheCookie; 12 | -------------------------------------------------------------------------------- /packages/utils/types/cacheSession.d.ts: -------------------------------------------------------------------------------- 1 | declare function get(key: string): any; 2 | declare function set(key: string, val: any): void; 3 | declare function remove(key: string): void; 4 | declare function clear(): void; 5 | declare const cacheSession: { 6 | get: typeof get; 7 | set: typeof set; 8 | remove: typeof remove; 9 | clear: typeof clear; 10 | }; 11 | export default cacheSession; 12 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/types.ts: -------------------------------------------------------------------------------- 1 | import { CommentType } from '@funblog/types'; 2 | 3 | export interface EditorProps { 4 | className?: string; 5 | placeholder?: string; 6 | } 7 | 8 | export type CommentProps = EditorProps & { 9 | path: string; 10 | page?: number; 11 | type?: CommentType; 12 | commentParams?: { 13 | postId?: number; 14 | pageId?: number; 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /apps/server/src/modules/init/init.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InitController } from './init.controller'; 3 | import { InitPublicController } from './init.public.controller'; 4 | import { InitService } from './init.service'; 5 | 6 | @Module({ 7 | controllers: [InitController, InitPublicController], 8 | providers: [InitService], 9 | }) 10 | export class InitModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/invitationCode/invitationCode.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InvitationCodeController } from './invitationCode.controller'; 3 | import { InvitationCodeService } from './invitationCode.service'; 4 | 5 | @Module({ 6 | controllers: [InvitationCodeController], 7 | providers: [InvitationCodeService], 8 | }) 9 | export class InvitationCodeModule {} 10 | -------------------------------------------------------------------------------- /apps/server/src/modules/link/link.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LinkController } from './link.controller'; 3 | import { LinkPublicController } from './link.public.controller'; 4 | import { LinkService } from './link.service'; 5 | 6 | @Module({ 7 | controllers: [LinkController, LinkPublicController], 8 | providers: [LinkService], 9 | }) 10 | export class LinkModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/page/page.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PageController } from './page.controller'; 3 | import { PagePublicController } from './page.public.controller'; 4 | import { PageService } from './page.service'; 5 | 6 | @Module({ 7 | controllers: [PageController, PagePublicController], 8 | providers: [PageService], 9 | }) 10 | export class PageModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/post/post.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PostController } from './post.controller'; 3 | import { PostPublicController } from './post.public.controller'; 4 | import { PostService } from './post.service'; 5 | 6 | @Module({ 7 | controllers: [PostController, PostPublicController], 8 | providers: [PostService], 9 | }) 10 | export class PostModule {} 11 | -------------------------------------------------------------------------------- /apps/server/src/modules/visitor/prisma/visitor.prisma: -------------------------------------------------------------------------------- 1 | model Visitor { 2 | id Int @id @default(autoincrement()) 3 | ip String @unique @db.VarChar(64) 4 | userAgent String? @db.Text 5 | country String? 6 | province String? 7 | city String? 8 | isp String? 9 | system Json? 10 | createdAt DateTime @default(now()) 11 | updatedAt DateTime @updatedAt 12 | } 13 | -------------------------------------------------------------------------------- /apps/admin/src/context/index.ts: -------------------------------------------------------------------------------- 1 | import { useStore, GlobalContext, GlobalProvider, GlobalContextProps } from './global'; 2 | import { useHelperStore, HelperContext, HelperProvider, HelperContextProps } from './helper'; 3 | 4 | export { useStore, GlobalContext, GlobalProvider }; 5 | export type { GlobalContextProps }; 6 | 7 | export { useHelperStore, HelperContext, HelperProvider }; 8 | export type { HelperContextProps }; 9 | -------------------------------------------------------------------------------- /apps/client/src/context/index.ts: -------------------------------------------------------------------------------- 1 | import { useStore, GlobalContext, GlobalProvider } from './global'; 2 | import { useHelperStore, HelperContext, HelperProvider } from './helper'; 3 | import { usePageStore, PageContext, PageProvider } from './page'; 4 | 5 | export { useStore, GlobalContext, GlobalProvider }; 6 | export { usePageStore, PageContext, PageProvider }; 7 | export { useHelperStore, HelperContext, HelperProvider }; 8 | -------------------------------------------------------------------------------- /apps/admin/src/app/Motion.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { LazyMotion } from 'framer-motion'; 4 | 5 | const loadFeatures = () => import('@/utils/features').then((res) => res.default); 6 | 7 | function Motion({ children }: { children: React.ReactNode }) { 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | } 14 | 15 | export default Motion; 16 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/link.ts: -------------------------------------------------------------------------------- 1 | export enum LinkStatus { 2 | REVIEW = 'REVIEW', 3 | SUCCESS = 'SUCCESS', 4 | REFUSE = 'REFUSE', 5 | } 6 | 7 | export interface Link { 8 | _id: string; 9 | title: string; 10 | url: string; 11 | desc: string; 12 | type: string; 13 | logo: string; 14 | isVisible: boolean; 15 | status: LinkStatus; 16 | userId?: string; 17 | refuseReason?: string; 18 | } 19 | -------------------------------------------------------------------------------- /apps/admin/src/context/helper/provider.tsx: -------------------------------------------------------------------------------- 1 | import { setMetaDataServerUrl } from '@/config/metaData'; 2 | import { HelperContext, HelperContextProps } from './context'; 3 | 4 | export const HelperProvider = ({ children, ...props }: { children: React.ReactNode } & HelperContextProps) => { 5 | setMetaDataServerUrl(props.serverUrl); 6 | return {children}; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/client/src/context/helper/provider.tsx: -------------------------------------------------------------------------------- 1 | import { setMetaDataServerUrl } from '@/config/metaData'; 2 | import { HelperContext, HelperContextProps } from './context'; 3 | 4 | export const HelperProvider = ({ children, ...props }: { children: React.ReactNode } & HelperContextProps) => { 5 | setMetaDataServerUrl(props.serverUrl); 6 | return {children}; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/prettier-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@funblog/prettier-config", 3 | "version": "2.0.7", 4 | "description": "funblog configuration file", 5 | "main": "index.js", 6 | "keywords": [ 7 | "prettier", 8 | "config" 9 | ], 10 | "author": "wintermelon", 11 | "license": "ISC", 12 | "dependencies": { 13 | "prettier": "^3.0.3", 14 | "prettier-plugin-tailwindcss": "^0.5.6" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/search/page.tsx: -------------------------------------------------------------------------------- 1 | import Main from './Main'; 2 | 3 | export async function generateMetadata({ searchParams }: { searchParams: { keyword: string } }) { 4 | return { 5 | title: `搜索:${searchParams.keyword}`, 6 | }; 7 | } 8 | 9 | function PostPage({ searchParams }: { searchParams: { keyword: string } }) { 10 | return
; 11 | } 12 | 13 | export default PostPage; 14 | -------------------------------------------------------------------------------- /apps/client/src/components/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import NextLink, { LinkProps } from 'next/link'; 2 | import React from 'react'; 3 | 4 | const Link = React.forwardRef< 5 | HTMLAnchorElement, 6 | React.AnchorHTMLAttributes & React.RefAttributes & LinkProps 7 | >(({ scroll = true, ...props }, ref) => { 8 | return ; 9 | }); 10 | 11 | export default Link; 12 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Cover/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | 3 | import Img from '../Image'; 4 | function Cover({ image, className }: { image: string; className?: string }) { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default Cover; 13 | -------------------------------------------------------------------------------- /apps/server/src/modules/visitor/visitor.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { VisitorController } from './visitor.controller'; 3 | import { VisitorPublicController } from './visitor.public.controller'; 4 | import { VisitorService } from './visitor.service'; 5 | 6 | @Module({ 7 | controllers: [VisitorController, VisitorPublicController], 8 | providers: [VisitorService], 9 | }) 10 | export class VisitorModule {} 11 | -------------------------------------------------------------------------------- /apps/client/src/context/global/hooks/usePc.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import useResizeValue from '@/hooks/useResizeValue'; 4 | 5 | const pcScreenSize = 768; 6 | /* */ 7 | export default function usePc() { 8 | const [pc, setPc] = useState(true); 9 | const width = useResizeValue(); 10 | useEffect(() => { 11 | setPc(width > pcScreenSize); 12 | }, [width]); 13 | return { 14 | pc, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /apps/server/src/dtos/pagination.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { IsNumber, IsOptional } from 'class-validator'; 3 | 4 | // 分页请求DTO 5 | export class PaginationDto { 6 | @IsOptional() 7 | @Type() 8 | @IsNumber() 9 | page?: number; 10 | 11 | @IsOptional() 12 | @Type() 13 | @IsNumber() 14 | pageSize?: number; 15 | 16 | // sql忽略条数 17 | skip?: number; 18 | // sql返回条数 19 | take?: number; 20 | } 21 | -------------------------------------------------------------------------------- /apps/server/src/modules/email/email.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { Public } from 'src/decorators/public'; 3 | import { EmailService } from './email.service'; 4 | 5 | @Public() 6 | @Controller('email') 7 | export class EmailController { 8 | constructor(private readonly commentService: EmailService) {} 9 | @Get('test') 10 | test() { 11 | return this.commentService.test(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/GlobalData/Update.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { User } from '@funblog/types'; 3 | import { useEffect } from 'react'; 4 | import { useStore } from '@/context'; 5 | 6 | function Update({ userInfo }: { userInfo: User }) { 7 | const { updateUserInfo } = useStore(); 8 | useEffect(() => { 9 | updateUserInfo(userInfo); 10 | }, [updateUserInfo, userInfo]); 11 | return <>; 12 | } 13 | 14 | export default Update; 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/tag/tag.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { Public } from 'src/decorators/public'; 3 | import { TagService } from './tag.service'; 4 | 5 | @Public() 6 | @Controller('p/tag') 7 | export class TagPublicController { 8 | constructor(private readonly tagService: TagService) {} 9 | 10 | @Get('list') 11 | findAll() { 12 | return this.tagService.findAll(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/play.ts: -------------------------------------------------------------------------------- 1 | import { BlogTag } from './blogTag'; 2 | 3 | export interface Play { 4 | _id: string; 5 | title: string; 6 | desc: string; 7 | tags: BlogTag[]; 8 | github: string; 9 | url: string; 10 | file: string; 11 | folder: string[]; 12 | fileType: 'file' | 'folder'; 13 | cover: string; 14 | source: string; 15 | download_nums: number; 16 | isVisible: boolean; 17 | createTime: string; 18 | } 19 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/DndKit/Droppable.tsx: -------------------------------------------------------------------------------- 1 | import { useDroppable } from '@dnd-kit/core'; 2 | import React from 'react'; 3 | 4 | export function Droppable(props: any) { 5 | const { isOver, setNodeRef } = useDroppable({ 6 | id: props.id, 7 | }); 8 | const style = { 9 | color: isOver ? 'green' : undefined, 10 | }; 11 | 12 | return ( 13 |
14 | {props.children} 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/client/src/components/Popup/index.tsx: -------------------------------------------------------------------------------- 1 | import { Portal } from '@funblog/components'; 2 | import React from 'react'; 3 | import Main, { PopupProps as Props } from './Main'; 4 | export type PopupProps = Props; 5 | function Popup(props: PopupProps) { 6 | if (props.appendBody) { 7 | return ( 8 | 9 |
10 | 11 | ); 12 | } 13 | return
; 14 | } 15 | 16 | export default Popup; 17 | -------------------------------------------------------------------------------- /apps/admin/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './permission'; 2 | export * from './role'; 3 | export * from './user'; 4 | export * from './site'; 5 | export * from './image'; 6 | export * from './category'; 7 | export * from './tag'; 8 | export * from './post'; 9 | export * from './invitationCode'; 10 | export * from './page'; 11 | export * from './comment'; 12 | export * from './visitor'; 13 | export * from './link'; 14 | export * from './svg'; 15 | export * from './init'; 16 | -------------------------------------------------------------------------------- /apps/admin/src/components/Button/ButtonTableText.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import { ReactNode } from 'react'; 3 | function ButtonTableText({ 4 | onClick, 5 | className, 6 | children, 7 | }: { 8 | onClick?: () => void; 9 | className?: string; 10 | children: ReactNode; 11 | }) { 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default ButtonTableText; 20 | -------------------------------------------------------------------------------- /apps/admin/src/components/PanelSetting/style.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | .item { 3 | width: 100% !important; 4 | } 5 | :global { 6 | .ant-form-item-label { 7 | width: 140px; 8 | } 9 | .ant-form-item-control { 10 | flex: 1; 11 | } 12 | .ant-pro-form-list-container { 13 | width: 100%; 14 | } 15 | .ant-space-item { 16 | flex: 1; 17 | } 18 | .ant-space { 19 | width: 100%; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/tag/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import PostPage from '@/proComponents/PostPage'; 2 | 3 | function Page({ params }: { params: { slug: string[] } }) { 4 | const slug = params.slug || []; 5 | const id = slug[0]; 6 | const value = slug[2]; 7 | return ( 8 | 14 | ); 15 | } 16 | 17 | export const revalidate = 0; 18 | export default Page; 19 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/NavigationSidebar/components/Tags/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@/components/Button'; 2 | import { useStore } from '@/context'; 3 | 4 | function Tags() { 5 | const { tagList = [] } = useStore(); 6 | return ( 7 | <> 8 | {tagList.map((tag) => ( 9 | 12 | ))} 13 | 14 | ); 15 | } 16 | 17 | export default Tags; 18 | -------------------------------------------------------------------------------- /apps/server/src/modules/appConfig/appConfig.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, forwardRef } from '@nestjs/common'; 2 | import { AppConfigService } from './appConfig.service'; 3 | import { EmailModule } from '../email/email.module'; 4 | import { SiteModule } from '../site/site.module'; 5 | 6 | @Module({ 7 | providers: [AppConfigService], 8 | exports: [AppConfigService], 9 | imports: [forwardRef(() => SiteModule), EmailModule], 10 | }) 11 | export class AppConfigModule {} 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/counter.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | 3 | interface CounterDocument extends db.Document { 4 | _id: string; 5 | name: string; 6 | count: number; 7 | } 8 | 9 | const CounterModel = db.model( 10 | 'counter', 11 | new db.Schema({ 12 | name: String, 13 | count: Number, 14 | createTime: { type: Date, default: Date.now }, 15 | }), 16 | ); 17 | 18 | export { CounterModel, CounterDocument }; 19 | -------------------------------------------------------------------------------- /packages/hooks/src/useMount.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from 'react'; 2 | 3 | export default function useMount(isRender?: boolean) { 4 | const ref = useRef({ 5 | mounted: false, 6 | }); 7 | const [mounted, setMounted] = useState(false); 8 | useEffect(() => { 9 | if (isRender) { 10 | setMounted(true); 11 | } 12 | ref.current.mounted = true; 13 | }, [isRender]); 14 | return isRender ? mounted : ref.current.mounted; 15 | } 16 | -------------------------------------------------------------------------------- /apps/admin/src/components/PanelSetting/Title.tsx: -------------------------------------------------------------------------------- 1 | function PanelTitle({ title }: { title: string }) { 2 | return ( 3 |
4 | 5 | {title} 6 | 7 | 8 |
9 | ); 10 | } 11 | 12 | export default PanelTitle; 13 | -------------------------------------------------------------------------------- /apps/admin/src/middlewares/composeMiddlewares.ts: -------------------------------------------------------------------------------- 1 | import { NextMiddleware, NextResponse } from 'next/server'; 2 | import { MiddlewareFactory } from './types'; 3 | 4 | export function composeMiddlewares(functions: MiddlewareFactory[] = [], index = 0): NextMiddleware { 5 | const current = functions[index]; 6 | if (current) { 7 | const next = composeMiddlewares(functions, index + 1); 8 | return current(next); 9 | } 10 | return () => NextResponse.next(); 11 | } 12 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/messages/page/[page]/UpdatePagination.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { usePageStore } from '@/context'; 5 | 6 | function UpdatePagination({ page }: { page: number }) { 7 | const { updatePagination } = usePageStore(); 8 | useEffect(() => { 9 | updatePagination({ 10 | page, 11 | }); 12 | }, [updatePagination, page]); 13 | return <>; 14 | } 15 | 16 | export default UpdatePagination; 17 | -------------------------------------------------------------------------------- /apps/client/src/components/Github/style.css: -------------------------------------------------------------------------------- 1 | 2 | .github-corner { 3 | width: 80px; 4 | height: 80px; 5 | border: 0; 6 | 7 | &:hover .octo-arm { 8 | animation: octocat-wave 560ms ease-in-out; 9 | } 10 | } 11 | 12 | @keyframes octocat-wave { 13 | 0%, 14 | 100% { 15 | transform: rotate(0); 16 | } 17 | 18 | 20%, 19 | 60% { 20 | transform: rotate(-25deg); 21 | } 22 | 23 | 40%, 24 | 80% { 25 | transform: rotate(10deg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/client/src/middlewares/composeMiddlewares.ts: -------------------------------------------------------------------------------- 1 | import { NextMiddleware, NextResponse } from 'next/server'; 2 | import { MiddlewareFactory } from './types'; 3 | 4 | export function composeMiddlewares(functions: MiddlewareFactory[] = [], index = 0): NextMiddleware { 5 | const current = functions[index]; 6 | if (current) { 7 | const next = composeMiddlewares(functions, index + 1); 8 | return current(next); 9 | } 10 | return () => NextResponse.next(); 11 | } 12 | -------------------------------------------------------------------------------- /apps/server/src/modules/permission/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | 4 | export class CreatePermissionDto { 5 | @IsNotEmpty({ 6 | message: '权限名称不能为空', 7 | }) 8 | readonly name: string; 9 | 10 | @IsNotEmpty({ 11 | message: '权限code不能为空', 12 | }) 13 | readonly code: string; 14 | } 15 | 16 | export class UpdatePermissionDto extends PartialType(CreatePermissionDto) {} 17 | -------------------------------------------------------------------------------- /apps/server/src/modules/visitor/visitor.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { Public } from 'src/decorators/public'; 4 | import { VisitorService } from './visitor.service'; 5 | 6 | @Public() 7 | @Controller('p/visitor') 8 | export class VisitorPublicController { 9 | constructor(private readonly visitorService: VisitorService) {} 10 | @Get('count') 11 | count() { 12 | return this.visitorService.count(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/image/prisma/image.prisma: -------------------------------------------------------------------------------- 1 | import { User } from "../../user/prisma/user" 2 | 3 | model Image { 4 | id Int @id @default(autoincrement()) 5 | originalname String 6 | filename String 7 | originalUrl String 8 | url String 9 | thumbnailUrl String? 10 | metadata Json 11 | createdAt DateTime @default(now()) 12 | user User @relation(fields: [userId], references: [id]) 13 | userId Int 14 | } 15 | 16 | -------------------------------------------------------------------------------- /apps/client/src/hooks/useAutoScrollAnchor.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import useHash from './useHash'; 3 | export default function useAutoScrollAnchor() { 4 | const hash = useHash(); 5 | useEffect(() => { 6 | if (hash) { 7 | const target = document.getElementById(hash); 8 | if (target) { 9 | requestAnimationFrame(() => { 10 | target.scrollIntoView({ behavior: 'smooth' }); 11 | }); 12 | } 13 | } 14 | }, [hash]); 15 | } 16 | -------------------------------------------------------------------------------- /apps/admin/src/context/global/context.ts: -------------------------------------------------------------------------------- 1 | import { SiteMeta, User } from '@funblog/types'; 2 | import { createContext } from 'react'; 3 | 4 | export interface GlobalContextProps { 5 | userInfo?: Partial; 6 | siteMeta?: Partial; 7 | } 8 | export interface GlobalContextData extends GlobalContextProps { 9 | // setUserInfo: (userInfo: Partial) => void; 10 | reloadUserInfo: () => Promise; 11 | } 12 | export const GlobalContext = createContext({} as GlobalContextData); 13 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/post/[id]/Meta/index.tsx: -------------------------------------------------------------------------------- 1 | import { Post } from '@funblog/types'; 2 | import Github from '@/components/Github'; 3 | 4 | function Meta({ post }: { post: Post }) { 5 | return ( 6 |
7 | {post.github && ( 8 |
9 | 10 |
11 | )} 12 |
13 | ); 14 | } 15 | 16 | export default Meta; 17 | -------------------------------------------------------------------------------- /apps/client/src/styles/components/shadow.css: -------------------------------------------------------------------------------- 1 | @layer components { 2 | .shadow-text-banner { 3 | /* font-family: theme(fontFamily.pmzd); */ 4 | font-style: italic; 5 | text-shadow: theme(colors.white/0.6); 6 | mask: linear-gradient(theme(colors.black) 20%, theme(colors.black/0.6) 70%) 0 0/100% 1.3em; 7 | } 8 | 9 | 10 | .shadow-text { 11 | text-shadow: -1px -1px theme(colors.white/0.5), 2px 2px 2px theme(colors.gray/0.3); 12 | color: theme(colors.text); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/permission/prisma/permission.prisma: -------------------------------------------------------------------------------- 1 | import { RolePermission } from "../../role/prisma/role" 2 | 3 | model Permission { 4 | id Int @id @default(autoincrement()) 5 | // 名称 6 | name String @db.VarChar(255) 7 | // 标识 8 | code String? @unique @db.VarChar(255) 9 | createdAt DateTime @default(now()) 10 | updatedAt DateTime @updatedAt 11 | rolePermissions RolePermission[] 12 | } 13 | -------------------------------------------------------------------------------- /packages/eslint-config/web.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['next/core-web-vitals', './common-ts'], 3 | plugins: ['react-hooks'], 4 | rules: { 5 | 'react-hooks/rules-of-hooks': 'error', 6 | 'react-hooks/exhaustive-deps': 'warn', 7 | 'react/display-name': 'off', 8 | 'no-undef': 'off', 9 | '@next/next/no-html-link-for-pages': 'off', 10 | "jsx-a11y/alt-text": "off", 11 | "import/export": "off", 12 | "@typescript-eslint/no-var-requires": "off" 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/search/page/[page]/page.tsx: -------------------------------------------------------------------------------- 1 | import Main from '../../Main'; 2 | 3 | export async function generateMetadata({ searchParams }: { searchParams: { keyword: string } }) { 4 | return { 5 | title: `搜索:${searchParams.keyword}`, 6 | }; 7 | } 8 | 9 | function PostPage({ params, searchParams }: { params: { page: string }; searchParams: { keyword: string } }) { 10 | return
; 11 | } 12 | 13 | export default PostPage; 14 | -------------------------------------------------------------------------------- /packages/hooks/src/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | function useDebounce(value: T, delay = 300) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | useEffect(() => { 6 | const handler = setTimeout(() => { 7 | setDebouncedValue(value); 8 | }, delay); 9 | 10 | return () => { 11 | clearTimeout(handler); 12 | }; 13 | }, [value, delay]); 14 | 15 | return debouncedValue; 16 | } 17 | 18 | export default useDebounce; 19 | -------------------------------------------------------------------------------- /apps/admin/src/config/metaData.ts: -------------------------------------------------------------------------------- 1 | import { SiteMeta } from '@funblog/types'; 2 | 3 | const METADATA = { 4 | serverUrl: process.env.SERVER_URL || '', 5 | siteMeta: {} as Partial, 6 | }; 7 | 8 | export const setMetaDataSiteMeta = (siteMeta?: Partial) => { 9 | METADATA.siteMeta = siteMeta || ({} as Partial); 10 | }; 11 | 12 | export const setMetaDataServerUrl = (serverUrl?: string) => { 13 | METADATA.serverUrl = serverUrl || ''; 14 | }; 15 | 16 | export { METADATA }; 17 | -------------------------------------------------------------------------------- /apps/client/src/config/metaData.ts: -------------------------------------------------------------------------------- 1 | import { SiteMeta } from '@funblog/types'; 2 | 3 | const METADATA = { 4 | serverUrl: process.env.SERVER_URL || '', 5 | siteMeta: {} as Partial, 6 | }; 7 | 8 | export const setMetaDataSiteMeta = (siteMeta?: Partial) => { 9 | METADATA.siteMeta = siteMeta || ({} as Partial); 10 | }; 11 | 12 | export const setMetaDataServerUrl = (serverUrl?: string) => { 13 | METADATA.serverUrl = serverUrl || ''; 14 | }; 15 | 16 | export { METADATA }; 17 | -------------------------------------------------------------------------------- /apps/admin/src/app/Provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { App, ConfigProvider } from 'antd'; 3 | import { HelperContextProps, HelperProvider } from '@/context'; 4 | function Provider({ children, ...props }: { children: React.ReactNode } & Partial) { 5 | return ( 6 | 7 | 8 | {children} 9 | 10 | 11 | ); 12 | } 13 | 14 | export default Provider; 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; 2 | import { PrismaClient } from 'node_modules/.prisma/client'; 3 | 4 | export const prisma = new PrismaClient(); 5 | @Injectable() 6 | export class PrismaService implements OnModuleInit, OnModuleDestroy { 7 | prisma = prisma; 8 | async onModuleInit() { 9 | await prisma.$connect(); 10 | } 11 | 12 | async onModuleDestroy() { 13 | await prisma.$disconnect(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/messages/page/[page]/page.tsx: -------------------------------------------------------------------------------- 1 | import { CommentType } from '@funblog/types'; 2 | import Comment from '@/proComponents/Comment'; 3 | import UpdatePagination from './UpdatePagination'; 4 | 5 | async function Messages({ params }: { params: { page: string } }) { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default Messages; 15 | -------------------------------------------------------------------------------- /packages/hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | import useDebounce from './useDebounce'; 2 | import useDisabledScrollByMask from './useDisabledScrollByMask'; 3 | import useMount from './useMount'; 4 | import useOutsideClick from './useOutsideClick'; 5 | import usePrevious from './usePrevious'; 6 | import useResize from './useResize'; 7 | import useScroll from './useScroll'; 8 | import useSize from './useSize'; 9 | 10 | export { usePrevious, useMount, useDebounce, useResize, useScroll, useSize, useOutsideClick, useDisabledScrollByMask }; 11 | -------------------------------------------------------------------------------- /packages/hooks/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import useDebounce from './useDebounce'; 2 | import useDisabledScrollByMask from './useDisabledScrollByMask'; 3 | import useMount from './useMount'; 4 | import useOutsideClick from './useOutsideClick'; 5 | import usePrevious from './usePrevious'; 6 | import useResize from './useResize'; 7 | import useScroll from './useScroll'; 8 | import useSize from './useSize'; 9 | export { usePrevious, useMount, useDebounce, useResize, useScroll, useSize, useOutsideClick, useDisabledScrollByMask }; 10 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/components/Emoji/styles.module.css: -------------------------------------------------------------------------------- 1 | body .emojiTooltip { 2 | width: 300px; 3 | height: 360px; 4 | padding: 0; 5 | background-color: theme(colors.white); 6 | color: theme(colors.text); 7 | pointer-events: auto; 8 | opacity: 1; 9 | box-shadow: 10 | 0 0 0 1px theme(colors.white) inset, 11 | 0 0 1px theme(colors.gray/0.1), 12 | 0 8px 20px theme(colors.gray/0.25); 13 | } 14 | 15 | 16 | .scroll { 17 | scroll-padding-top: 0.5em; 18 | scroll-behavior: smooth; 19 | } -------------------------------------------------------------------------------- /apps/client/src/hooks/usePreviewImage.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import PreviewImage from '@/components/PreviewImage'; 3 | 4 | export default function usePreviewImage() { 5 | useEffect(() => { 6 | document.body.addEventListener('click', (e) => { 7 | const target = e.target as HTMLImageElement; 8 | if (target.className === 'preview-image') { 9 | const src = target.src; 10 | PreviewImage.show({ 11 | src, 12 | }); 13 | } 14 | }); 15 | }, []); 16 | } 17 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/GlobalData/GetData.tsx: -------------------------------------------------------------------------------- 1 | import { TOKEN_KEY } from '@funblog/constants'; 2 | import { cookies, headers } from 'next/headers'; 3 | import { getUserInfo, createVisitor } from '@/api'; 4 | import Update from './Update'; 5 | 6 | async function GetData() { 7 | const userInfo = await getUserInfo(cookies().get(TOKEN_KEY)?.value); 8 | createVisitor({ 9 | userAgent: headers().get('user-agent') as string, 10 | }); 11 | return ; 12 | } 13 | 14 | export default GetData; 15 | -------------------------------------------------------------------------------- /apps/server/src/modules/comment/comment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CommentController } from './comment.controller'; 3 | import { CommentPublicController } from './comment.public.controller'; 4 | import { CommentService } from './comment.service'; 5 | import { EmailModule } from '../email/email.module'; 6 | 7 | @Module({ 8 | controllers: [CommentController, CommentPublicController], 9 | providers: [CommentService], 10 | imports: [EmailModule], 11 | }) 12 | export class CommentModule {} 13 | -------------------------------------------------------------------------------- /opts/write-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 获取文件路径和写入内容 3 | file_path=$1 4 | content=$2 5 | 6 | 7 | # 检查参数是否完整 8 | if [ -z "${file_path}" ] || [ -z "${content}" ]; then 9 | echo "请提供完整参数!" 10 | exit 1 11 | fi 12 | 13 | new_data="${content}\n" 14 | 15 | if [ -f "$file_path" ]; then 16 | existing_data=$(cat $file_path) 17 | new_data="${content}\n${existing_data}" 18 | fi 19 | 20 | # 写入内容到文件 21 | echo "$new_data" > "$file_path" 22 | if [ $? -eq 0 ]; then 23 | echo "内容已成功写入到文件中!" 24 | else 25 | echo "写入文件时发生错误!" 26 | fi 27 | -------------------------------------------------------------------------------- /packages/types/lib/esm/comment.js: -------------------------------------------------------------------------------- 1 | var CommentStatus; 2 | (function (CommentStatus) { 3 | CommentStatus["APPROVED"] = "approved"; 4 | CommentStatus["PENDING"] = "pending"; 5 | CommentStatus["SPAM"] = "spam"; 6 | })(CommentStatus || (CommentStatus = {})); 7 | var CommentType; 8 | (function (CommentType) { 9 | CommentType["POST"] = "post"; 10 | CommentType["PAGE"] = "page"; 11 | CommentType["MESSAGE_BOARD"] = "message_board"; 12 | })(CommentType || (CommentType = {})); 13 | 14 | export { CommentStatus, CommentType }; 15 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Header/SearchBtn/index.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@funblog/components'; 2 | 3 | function SearchBtn({ onClick }: { onClick: () => void }) { 4 | return ( 5 |
9 | 10 |
11 | ); 12 | } 13 | 14 | export default SearchBtn; 15 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/NavigationSidebar/PC/withContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { usePageStore, useStore } from '@/context'; 3 | import NavigationSidebar, { NavigationSidebarProps } from './main'; 4 | 5 | function NavigationSidebarWithContext(props: Omit) { 6 | const { pc } = useStore(); 7 | const { showSidebar } = usePageStore(); 8 | return ; 9 | } 10 | 11 | export default NavigationSidebarWithContext; 12 | -------------------------------------------------------------------------------- /apps/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@funblog/tsconfig/web.config.json" 4 | ], 5 | "compilerOptions": { 6 | "plugins": [ 7 | { 8 | "name": "next" 9 | } 10 | ], 11 | "paths": { 12 | "@/*": [ 13 | "./src/*" 14 | ] 15 | }, 16 | "strictNullChecks": true, 17 | "downlevelIteration": true 18 | }, 19 | "include": [ 20 | "next-env.d.ts", 21 | "global.d.ts", 22 | "**/*.ts", 23 | "**/*.tsx", 24 | ".next/types/**/*.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /apps/server/src/modules/role/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsNotEmpty, IsArray } from 'class-validator'; 3 | 4 | export class CreateRoleDto { 5 | @IsNotEmpty({ 6 | message: '角色名称不能为空', 7 | }) 8 | name: string; 9 | 10 | @IsNotEmpty({ 11 | message: '角色code不能为空', 12 | }) 13 | code: string; 14 | 15 | @IsArray({ 16 | message: '请传入数组类型', 17 | }) 18 | permissions: number[]; 19 | } 20 | 21 | export class UpdateRoleDto extends PartialType(CreateRoleDto) {} 22 | -------------------------------------------------------------------------------- /apps/server/src/modules/svg/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; 3 | 4 | export class CreateSvgDto { 5 | @IsNotEmpty() 6 | content: string; 7 | 8 | @IsNotEmpty() 9 | name: string; 10 | 11 | @IsOptional() 12 | @IsArray() 13 | @IsString({ each: true }) 14 | scope?: string[]; 15 | 16 | @IsOptional() 17 | desc?: string; 18 | } 19 | 20 | export class UpdateSvgDto extends PartialType(CreateSvgDto) {} 21 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/echarts/Pie.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { useEchartsByPie } from './hooks'; 3 | import { parseObj } from './utils'; 4 | 5 | type Props = { 6 | name: string; 7 | data: any; 8 | }; 9 | 10 | const Pie = ({ name, data }: Props) => { 11 | const ref = useRef(null); 12 | useEchartsByPie({ 13 | ref, 14 | name, 15 | data: data ? parseObj(data) : [], 16 | }); 17 | return
; 18 | }; 19 | 20 | export default Pie; 21 | -------------------------------------------------------------------------------- /apps/client/src/hooks/useResizeValue.ts: -------------------------------------------------------------------------------- 1 | import { useResize } from '@funblog/hooks'; 2 | import { isDocument } from '@funblog/utils'; 3 | import { useCallback, useState } from 'react'; 4 | 5 | const getWidth = () => (isDocument() ? document.documentElement.clientWidth : 0); 6 | export default function useResizeValue() { 7 | const [value, setValue] = useState(getWidth()); 8 | const callback = useCallback(() => { 9 | setValue(getWidth()); 10 | }, []); 11 | useResize({ 12 | callback, 13 | }); 14 | return value; 15 | } 16 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /apps/admin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS base 2 | RUN corepack enable 3 | COPY . /app 4 | WORKDIR /app 5 | COPY ./opts/package.json /app 6 | 7 | 8 | 9 | FROM base AS build 10 | RUN pnpm install 11 | # 如需提前build, 需要传入SERVER_URL, 因为build是部分会静态生成,读取不到SERVER_URL,无法赋值 12 | RUN pnpm run build 13 | 14 | FROM base 15 | COPY --from=build /app/node_modules /app/node_modules 16 | COPY --from=build /app/.next /app/.next 17 | 18 | EXPOSE 8030 19 | # CMD ["sh", "-c", "pnpm run build && pnpm start"] 20 | CMD ["sh", "-c", "pnpm start"] 21 | 22 | 23 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/types/blog.ts: -------------------------------------------------------------------------------- 1 | import { BlogTag } from './blogTag'; 2 | export interface BlogSub { 3 | desc: string; 4 | github: string; 5 | cover: string; 6 | source: string; 7 | title: string; 8 | tags: BlogTag[]; 9 | isVisible: boolean; 10 | like_nums: number; 11 | comment_nums: number; 12 | read_nums: number; 13 | updateTime: string; 14 | createTime: string; 15 | _id: string; 16 | } 17 | 18 | export interface Blog extends BlogSub { 19 | md: string; 20 | html: string; 21 | hasLike?: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /packages/components/src/components/SvgBox/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import React from 'react'; 3 | import styles from '../../styles/index.module.css'; 4 | 5 | function SvgBox({ className, content }: { className?: string; content: string }) { 6 | return ( 7 | 15 | ); 16 | } 17 | 18 | export default SvgBox; 19 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/store/context.ts: -------------------------------------------------------------------------------- 1 | import { Comment, PageRes } from '@funblog/types'; 2 | import { createContext } from 'react'; 3 | import { CommentProps } from '../types'; 4 | 5 | export type CommentContextProps = Omit & { 6 | commentData?: PageRes; 7 | value: string; 8 | onChange: (value: string) => void; 9 | onAddText: (value: string) => void; 10 | updateCommentData: (data: PageRes) => void; 11 | }; 12 | export const CommentContext = createContext({} as CommentContextProps); 13 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/visitor.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { Visitor } from 'src/modules/mongodb/types/visitor'; 3 | 4 | interface VisitorDocument extends db.Document, Visitor { 5 | _id: string; 6 | } 7 | 8 | const VisitorModel = db.model( 9 | 'visitor', 10 | new db.Schema({ 11 | ip: String, 12 | userAgent: String, 13 | system: Object, 14 | location: Object, 15 | createTime: { type: Date, default: Date.now }, 16 | }), 17 | ); 18 | 19 | export { VisitorModel, VisitorDocument }; 20 | -------------------------------------------------------------------------------- /apps/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | /opts 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostContent/index.tsx: -------------------------------------------------------------------------------- 1 | import { Post } from '@funblog/types'; 2 | import LayoutContainer from '@/components/LayoutContainer'; 3 | import { ColCard } from '@/proComponents/PostCard'; 4 | 5 | async function PostContent({ className, postList }: { className?: string; postList: Post[] }) { 6 | return ( 7 | 8 | {postList.map((post) => ( 9 | 10 | ))} 11 | 12 | ); 13 | } 14 | 15 | export default PostContent; 16 | -------------------------------------------------------------------------------- /apps/admin/src/components/Editor/plugins/linkTarget.tsx: -------------------------------------------------------------------------------- 1 | import { BytemdPlugin } from 'bytemd'; 2 | import { visit } from 'unist-util-visit'; 3 | 4 | const aTargetPlugin = () => (tree: any) => { 5 | visit(tree, (node) => { 6 | if (node.type === 'element' && node.tagName === 'a') { 7 | node.properties.target = '_blank'; 8 | node.properties.rel = 'noopener noreferrer'; 9 | } 10 | }); 11 | }; 12 | 13 | export function LinkTarget(): BytemdPlugin { 14 | return { 15 | rehype: (processor) => processor.use(aTargetPlugin), 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormImageList/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form } from 'antd'; 2 | import { ComponentProps } from 'react'; 3 | import ImageList, { ImageListProps } from '@/components/ImageList'; 4 | 5 | type FormImageListProps = ComponentProps & { 6 | fieldProps?: Partial; 7 | }; 8 | 9 | function FormImageList({ fieldProps, ...props }: FormImageListProps) { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default FormImageList; 18 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.CommentStatus = void 0; 4 | (function (CommentStatus) { 5 | CommentStatus["APPROVED"] = "approved"; 6 | CommentStatus["PENDING"] = "pending"; 7 | CommentStatus["SPAM"] = "spam"; 8 | })(exports.CommentStatus || (exports.CommentStatus = {})); 9 | exports.CommentType = void 0; 10 | (function (CommentType) { 11 | CommentType["POST"] = "post"; 12 | CommentType["PAGE"] = "page"; 13 | CommentType["MESSAGE_BOARD"] = "message_board"; 14 | })(exports.CommentType || (exports.CommentType = {})); 15 | -------------------------------------------------------------------------------- /packages/types/src/rolePermission.ts: -------------------------------------------------------------------------------- 1 | export interface Role { 2 | id: number; 3 | name: string; 4 | code: string; 5 | createAt: string; 6 | updateAt: string; 7 | userId?: number; 8 | rolePermissions?: RolePermission[]; 9 | } 10 | 11 | export interface Permission { 12 | id: number; 13 | name: string; 14 | code: string; 15 | createAt: string; 16 | updateAt: string; 17 | } 18 | 19 | export interface RolePermission { 20 | id: number; 21 | roleId: number; 22 | permissionId: number; 23 | permission: Permission; 24 | role: Role; 25 | } 26 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import BackTop from '@/components/BackTop'; 2 | import Footer from '@/proComponents/Footer'; 3 | import GlobalData from '@/proComponents/GlobalData'; 4 | import LoginBtn from '@/proComponents/LoginBtn'; 5 | import DynamicLayout from './_layout'; 6 | 7 | export default function MainLayout({ children }: { children: React.ReactNode }) { 8 | return ( 9 | }> 10 | 11 | 12 | {children} 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/server/src/pipes/pagination.ts: -------------------------------------------------------------------------------- 1 | import { PAGESIZE } from '@funblog/constants'; 2 | import { Injectable, PipeTransform } from '@nestjs/common'; 3 | import { PaginationDto } from '../dtos/pagination.dto'; 4 | 5 | /** 6 | * 分页器管道 7 | */ 8 | @Injectable() 9 | export class PaginationPipe implements PipeTransform { 10 | transform(value: PaginationDto) { 11 | const take = value.pageSize ?? PAGESIZE; 12 | const skip = value.page ? (value.page - 1) * take : 0; 13 | 14 | value.take = take; 15 | value.skip = skip; 16 | return value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/category/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import PostPage from '@/proComponents/PostPage'; 2 | 3 | function Page({ params }: { params: { slug: string[] } }) { 4 | const slug = params.slug || []; 5 | const id = slug[0]; 6 | const value = slug[2]; 7 | return ( 8 | 14 | ); 15 | } 16 | 17 | // https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config 18 | export const revalidate = 0; 19 | 20 | export default Page; 21 | -------------------------------------------------------------------------------- /apps/client/src/components/BackTop/style.module.css: -------------------------------------------------------------------------------- 1 | .backTop { 2 | width: 34px; 3 | height: 34px; 4 | line-height: 34px; 5 | font-size: 34px; 6 | border-radius: 50%; 7 | position: fixed; 8 | right: 20px; 9 | bottom: 60px; 10 | text-align: center; 11 | transition: 0.5s; 12 | cursor: pointer; 13 | z-index: theme(zIndex.50); 14 | transform: translateX(100px); 15 | background-color: theme(colors.white/0.7); 16 | 17 | &:hover { 18 | color: theme(colors.primary); 19 | } 20 | 21 | &.show { 22 | transform: translateX(0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/client/src/components/LayoutContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | 3 | function LayoutContainer({ children, className }: { children: React.ReactNode; className?: string }) { 4 | return ( 5 |
15 | {children} 16 |
17 | ); 18 | } 19 | 20 | export default LayoutContainer; 21 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Header/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import { NormalImage } from '@/components/Image'; 2 | import Link from '@/components/Link'; 3 | import { useStore } from '@/context'; 4 | import { getResourceUrl } from '@/utils'; 5 | 6 | function Logo({ className }: { className?: string }) { 7 | const { siteMeta } = useStore(); 8 | return ( 9 | 10 | {siteMeta?.logo && } 11 | 12 | ); 13 | } 14 | 15 | export default Logo; 16 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/blogLike.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { BlogLike } from 'src/modules/mongodb/types/blogLike'; 3 | 4 | interface BlogLikeDocument extends db.Document, BlogLike { 5 | _id: string; 6 | } 7 | 8 | const BlogLikeModel = db.model( 9 | 'blog_like', 10 | new db.Schema({ 11 | articleId: db.Schema.Types.ObjectId, 12 | userId: db.Schema.Types.ObjectId, 13 | ip: String, 14 | createTime: { type: Date, default: Date.now }, 15 | }), 16 | ); 17 | 18 | export { BlogLikeModel, BlogLikeDocument }; 19 | -------------------------------------------------------------------------------- /apps/server/src/modules/invitationCode/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { Transform } from 'class-transformer'; 3 | import { IsArray, IsDate, IsNumber, IsOptional } from 'class-validator'; 4 | 5 | export class CreateInvitationCodeDto { 6 | @IsOptional() 7 | @IsDate() 8 | @Transform(({ value }) => new Date(value)) 9 | expiredAt: Date; 10 | 11 | @IsArray() 12 | @IsNumber({}, { each: true }) 13 | roles: number[]; 14 | } 15 | 16 | export class UpdateInvitationCodeDto extends PartialType(CreateInvitationCodeDto) {} 17 | -------------------------------------------------------------------------------- /packages/utils/src/highlight.ts: -------------------------------------------------------------------------------- 1 | import highlightJs from 'highlight.js/lib/core'; 2 | import css from 'highlight.js/lib/languages/css'; 3 | import javascript from 'highlight.js/lib/languages/javascript'; 4 | import nginx from 'highlight.js/lib/languages/nginx'; 5 | import typescript from 'highlight.js/lib/languages/typescript'; 6 | highlightJs.registerLanguage('javascript', javascript); 7 | highlightJs.registerLanguage('css', css); 8 | highlightJs.registerLanguage('typescript', typescript); 9 | highlightJs.registerLanguage('nginx', nginx); 10 | 11 | export default highlightJs; 12 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/echarts/PieFull.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { useEchartsScrollByPie } from './hooks'; 3 | import { parseData, parseObj } from './utils'; 4 | 5 | type Props = { 6 | name: string; 7 | data: any; 8 | }; 9 | 10 | const PieFull = ({ name, data }: Props) => { 11 | const ref = useRef(null); 12 | useEchartsScrollByPie({ 13 | ref, 14 | name, 15 | data: data ? parseData(parseObj(data)) : [], 16 | }); 17 | return
; 18 | }; 19 | 20 | export default PieFull; 21 | -------------------------------------------------------------------------------- /apps/client/src/app/(main)/aboutme/page.tsx: -------------------------------------------------------------------------------- 1 | import { Banner } from '@/proComponents/Banner'; 2 | import Forum from './Forum'; 3 | import Me from './Me'; 4 | import TechnologyStack from './TechnologyStack'; 5 | 6 | export const metadata = { 7 | title: '关于我', 8 | }; 9 | function About() { 10 | return ( 11 | <> 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | ); 20 | } 21 | 22 | export default About; 23 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | 3 | function IImage({ image, className }: { image: string; className?: string }) { 4 | return ( 5 |
15 | ); 16 | } 17 | 18 | export default IImage; 19 | -------------------------------------------------------------------------------- /apps/client/src/app/apis/clear-cache/route.ts: -------------------------------------------------------------------------------- 1 | import { revalidateTag } from 'next/cache'; 2 | import { type NextRequest } from 'next/server'; 3 | 4 | export async function GET(request: NextRequest) { 5 | const { searchParams } = new URL(request.url); 6 | const tag = searchParams.get('tag'); 7 | if (tag) { 8 | revalidateTag(tag); 9 | return Response.json({ 10 | code: 0, 11 | message: 'success', 12 | }); 13 | } 14 | return Response.json({ 15 | code: 1, 16 | message: 'fail', 17 | }); 18 | // const token = request.cookies.get('token') 19 | } 20 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/types.ts: -------------------------------------------------------------------------------- 1 | import type { UniqueIdentifier } from '@dnd-kit/core'; 2 | import type { MutableRefObject } from 'react'; 3 | 4 | export interface TreeItem { 5 | id: UniqueIdentifier; 6 | children: TreeItem[]; 7 | collapsed?: boolean; 8 | } 9 | 10 | export type TreeItems = TreeItem[]; 11 | 12 | export interface FlattenedItem extends TreeItem { 13 | parentId: UniqueIdentifier | null; 14 | depth: number; 15 | index: number; 16 | } 17 | 18 | export type SensorContext = MutableRefObject<{ 19 | items: FlattenedItem[]; 20 | offset: number; 21 | }>; 22 | -------------------------------------------------------------------------------- /packages/types/types/rolePermission.d.ts: -------------------------------------------------------------------------------- 1 | export interface Role { 2 | id: number; 3 | name: string; 4 | code: string; 5 | createAt: string; 6 | updateAt: string; 7 | userId?: number; 8 | rolePermissions?: RolePermission[]; 9 | } 10 | export interface Permission { 11 | id: number; 12 | name: string; 13 | code: string; 14 | createAt: string; 15 | updateAt: string; 16 | } 17 | export interface RolePermission { 18 | id: number; 19 | roleId: number; 20 | permissionId: number; 21 | permission: Permission; 22 | role: Role; 23 | } 24 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/blogTag.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { BlogTag } from 'src/modules/mongodb/types/blogTag'; 3 | 4 | interface BlogTagDocument extends db.Document, BlogTag { 5 | _id: string; 6 | } 7 | 8 | const BlogTagModel = db.model( 9 | 'blog_tag', 10 | new db.Schema({ 11 | name: String, 12 | icon: String, 13 | color: String, 14 | seq: Number, 15 | isVisible: { type: Boolean, default: true }, 16 | createTime: { type: Date, default: Date.now }, 17 | }), 18 | ); 19 | 20 | export { BlogTagModel, BlogTagDocument }; 21 | -------------------------------------------------------------------------------- /packages/eslint-config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:@typescript-eslint/recommended', 4 | './common-ts' 5 | ], 6 | plugins: ['@typescript-eslint/eslint-plugin'], 7 | parser: '@typescript-eslint/parser', 8 | root: true, 9 | env: { 10 | node: true, 11 | jest: true, 12 | }, 13 | rules: { 14 | '@typescript-eslint/interface-name-prefix': 'off', 15 | '@typescript-eslint/explicit-function-return-type': 'off', 16 | '@typescript-eslint/explicit-module-boundary-types': 'off', 17 | 'no-useless-constructor': 'off', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/utils/src/bindClickOutSide.ts: -------------------------------------------------------------------------------- 1 | export default function bindClickOutSide( 2 | target: Element | (() => Element | null), 3 | callback: (event: T) => void, 4 | ) { 5 | const _target = typeof target === 'function' ? target() : target; 6 | const _callback = callback; 7 | const handleClick = (e: any) => { 8 | if (_target && !_target.contains(e.target)) { 9 | _callback(e); 10 | } 11 | }; 12 | document.addEventListener('click', handleClick); 13 | 14 | return () => { 15 | document.removeEventListener('click', handleClick); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /apps/admin/src/components/ImageInfo/style.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | :global { 3 | tbody { 4 | .ant-descriptions-row { 5 | .ant-descriptions-item { 6 | padding-bottom: 0px; 7 | } 8 | &:first-child { 9 | display: none; 10 | } 11 | &:nth-child(2n) { 12 | .ant-descriptions-item { 13 | padding-bottom: 40px; 14 | } 15 | } 16 | &:nth-child(2n+1) { 17 | .ant-descriptions-item { 18 | padding-bottom: 8px; 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/client/src/hooks/useMainHeight.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { FOOTER_ID, HEADER_ID } from '@/constants'; 3 | 4 | export default function useMainHeight() { 5 | const [height, setHeight] = useState('calc(100vh - 56px - 144px)'); 6 | useEffect(() => { 7 | const headerH = document.getElementById(HEADER_ID)?.offsetHeight || 0; 8 | const footerH = document.getElementById(FOOTER_ID)?.offsetHeight || 0; 9 | const height = window.innerHeight - headerH - footerH; 10 | setHeight(`${height}px`); 11 | }, []); 12 | return { 13 | height, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /apps/admin/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /node_modules1 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | 10 | /opts 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # local env files 31 | .env*.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/media/image/style.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | :global { 3 | tbody { 4 | .ant-descriptions-row { 5 | .ant-descriptions-item { 6 | padding-bottom: 0px; 7 | } 8 | &:first-child { 9 | display: none; 10 | } 11 | &:nth-child(2n) { 12 | .ant-descriptions-item { 13 | padding-bottom: 40px; 14 | } 15 | } 16 | &:nth-child(2n+1) { 17 | .ant-descriptions-item { 18 | padding-bottom: 8px; 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/client/src/context/page/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export interface PageContextPagination { 4 | total: number; 5 | pageSize?: number; 6 | page?: number; 7 | } 8 | export interface PageContextProps { 9 | pagination?: PageContextPagination; 10 | } 11 | 12 | export interface PageContextData extends PageContextProps { 13 | showSidebar: boolean; 14 | setShowSidebar: React.Dispatch>; 15 | updatePagination: (pagination: Partial) => void; 16 | } 17 | export const Context = createContext({} as PageContextData); 18 | -------------------------------------------------------------------------------- /apps/client/src/api/page.ts: -------------------------------------------------------------------------------- 1 | import { Page, PageMenuButton } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function getPageInfo(alias: string) { 5 | return request.get(`/api/p/page/${alias}`); 6 | } 7 | 8 | export function getPageList() { 9 | return request.get('/api/p/page/list'); 10 | } 11 | 12 | export function getPageMenuNavigationList() { 13 | return request.get('/api/p/page/menu/navigation/list'); 14 | } 15 | 16 | export function getPageMenuSubList() { 17 | return request.get('/api/p/page/menu/sub/list'); 18 | } 19 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/userSystem.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { UserSystem } from 'src/modules/mongodb/types/userSystem'; 3 | 4 | interface UserSystemDocument extends db.Document, UserSystem { 5 | _id: string; 6 | } 7 | 8 | const UserSystemModel = db.model( 9 | 'user_system', 10 | new db.Schema({ 11 | name: String, 12 | username: String, 13 | roles: [String], 14 | password: String, 15 | github: String, 16 | createTime: { type: Date, default: Date.now }, 17 | }), 18 | ); 19 | 20 | export { UserSystemModel, UserSystemDocument }; 21 | -------------------------------------------------------------------------------- /apps/admin/src/components/Editor/plugins/heading.tsx: -------------------------------------------------------------------------------- 1 | import { BytemdPlugin } from 'bytemd'; 2 | import { visit } from 'unist-util-visit'; 3 | 4 | const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; 5 | const headingPlugin = () => (tree: any) => { 6 | visit(tree, (node) => { 7 | if (node.type === 'element' && headings.includes(node.tagName)) { 8 | const title = node.children[0]?.value; 9 | node.properties['data-id'] = title; 10 | } 11 | }); 12 | }; 13 | 14 | export function Heading(): BytemdPlugin { 15 | return { 16 | rehype: (processor) => processor.use(headingPlugin), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /apps/server/src/modules/invitationCode/prisma/invitationCode.prisma: -------------------------------------------------------------------------------- 1 | import { Role } from "../../role/prisma/role" 2 | import { User } from "../../user/prisma/user" 3 | 4 | model InvitationCode { 5 | id Int @id @default(autoincrement()) 6 | // 标识 7 | code String @unique 8 | createdAt DateTime @default(now()) 9 | updatedAt DateTime @updatedAt 10 | expiredAt DateTime 11 | roles Role[] 12 | 13 | user User @relation("createInvitationCodes", fields: [userId], references: [id]) 14 | userId Int 15 | 16 | registerUsers User[] @relation("registerInvitationCode") 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /apps/server/src/modules/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; 2 | import { LoggerMiddleware } from 'src/middlewares/logger.middleware'; 3 | import { BcryptService } from './bcrypt.service'; 4 | import { LoggerService } from './logger.service'; 5 | 6 | @Global() 7 | @Module({ 8 | providers: [BcryptService, LoggerService], 9 | exports: [BcryptService, LoggerService], 10 | }) 11 | export class SharedModule implements NestModule { 12 | configure(consumer: MiddlewareConsumer) { 13 | consumer.apply(LoggerMiddleware).forRoutes('*'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormList/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form } from 'antd'; 2 | import { ComponentProps } from 'react'; 3 | import List, { ListProps } from './List'; 4 | 5 | // type FormListCheckboxProps = ComponentProps & { 6 | // listProps?: ListProps; 7 | // }; 8 | type FormListCheckboxProps = ComponentProps & Pick; 9 | 10 | function FormListCheckbox({ request, ...props }: FormListCheckboxProps) { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default FormListCheckbox; 19 | -------------------------------------------------------------------------------- /apps/client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS base 2 | RUN corepack enable 3 | COPY . /app 4 | WORKDIR /app 5 | COPY ./opts/package.json /app 6 | 7 | # ARG SERVER_URL 8 | ENV DOCKER=true 9 | 10 | 11 | 12 | FROM base AS build 13 | RUN pnpm install && pnpm add sharp 14 | # RUN pnpm run build 15 | 16 | 17 | FROM base 18 | COPY --from=build /app/node_modules /app/node_modules 19 | # COPY --from=build /app/.next /app/.next 20 | 21 | EXPOSE 8010 22 | # build迁移到启动的时候运行,因为需要请求server服务,build 跟 runtime 是分开的,build是请求不到server 23 | # CMD ["sh", "-c", "pnpm start"] 24 | CMD ["sh", "-c", "pnpm run build && pnpm start"] 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/types/lib/esm/page.js: -------------------------------------------------------------------------------- 1 | var PageMenuType; 2 | (function (PageMenuType) { 3 | PageMenuType["NAVIGATION_NAV"] = "navigation-nav"; 4 | PageMenuType["SUB_NAV"] = "sub-nav"; 5 | })(PageMenuType || (PageMenuType = {})); 6 | var PageMenuButtonType; 7 | (function (PageMenuButtonType) { 8 | PageMenuButtonType["URL"] = "url"; 9 | PageMenuButtonType["CATEGORY"] = "category"; 10 | PageMenuButtonType["ARTICLE"] = "article"; 11 | PageMenuButtonType["TAG"] = "tag"; 12 | PageMenuButtonType["PAGE"] = "page"; 13 | })(PageMenuButtonType || (PageMenuButtonType = {})); 14 | 15 | export { PageMenuButtonType, PageMenuType }; 16 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormText/index.tsx: -------------------------------------------------------------------------------- 1 | import { ProFormText } from '@ant-design/pro-components'; 2 | import { Rule } from 'antd/es/form'; 3 | import { ComponentProps } from 'react'; 4 | import { getFormItemDefaultProps } from '@/utils'; 5 | 6 | export type FormTextProps = ComponentProps & { 7 | required?: boolean; 8 | extendRules?: Rule[]; 9 | }; 10 | 11 | function FormText(props: FormTextProps) { 12 | const { rules, placeholder } = getFormItemDefaultProps(props); 13 | return ; 14 | } 15 | 16 | export default FormText; 17 | -------------------------------------------------------------------------------- /packages/hooks/src/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | export type ShouldUpdateFunc = (prev: T | undefined, next: T) => boolean; 4 | 5 | const defaultShouldUpdate = (a?: T, b?: T) => !Object.is(a, b); 6 | 7 | function usePrevious(state: T, shouldUpdate: ShouldUpdateFunc = defaultShouldUpdate): T | undefined { 8 | const prevRef = useRef(); 9 | const curRef = useRef(); 10 | 11 | if (shouldUpdate(curRef.current, state)) { 12 | prevRef.current = curRef.current; 13 | curRef.current = state; 14 | } 15 | 16 | return prevRef.current; 17 | } 18 | 19 | export default usePrevious; 20 | -------------------------------------------------------------------------------- /apps/admin/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/client/src/components/ALink/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = { 4 | href: string; 5 | target?: '_blank' | '_self' | '_parent' | '_top'; 6 | className?: string; 7 | style?: any; 8 | onClick?: (event: React.MouseEvent) => void; 9 | children: any; 10 | }; 11 | 12 | const ALink = ({ href = 'javascript:;', target = '_self', className, style, onClick, children }: Props) => { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | 20 | export default ALink; 21 | -------------------------------------------------------------------------------- /apps/server/src/modules/link/link.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from '@nestjs/common'; 2 | import { Public } from 'src/decorators/public'; 3 | import { CreateLinkDto } from './dto/request.dto'; 4 | import { LinkService } from './link.service'; 5 | 6 | @Public() 7 | @Controller('p/link') 8 | export class LinkPublicController { 9 | constructor(private readonly linkService: LinkService) {} 10 | 11 | @Post() 12 | create(@Body() body: CreateLinkDto) { 13 | return this.linkService.create(body); 14 | } 15 | 16 | @Get('list') 17 | list() { 18 | return this.linkService.list(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/server/src/modules/site/site.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, forwardRef } from '@nestjs/common'; 2 | import { SiteController } from './site.controller'; 3 | import { SitePublicController } from './site.public.controller'; 4 | import { SiteService } from './site.service'; 5 | import { AppConfigModule } from '../appConfig/appConfig.module'; 6 | import { HttpModule } from '../http/http.module'; 7 | 8 | @Module({ 9 | imports: [forwardRef(() => AppConfigModule), HttpModule], 10 | controllers: [SiteController, SitePublicController], 11 | providers: [SiteService], 12 | exports: [SiteService], 13 | }) 14 | export class SiteModule {} 15 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@funblog/types", 3 | "version": "2.0.8", 4 | "main": "lib/cjs/index.js", 5 | "module": "lib/esm/index.js", 6 | "types": "types/index.d.ts", 7 | "description": "typescript types for funblog", 8 | "keywords": [ 9 | "typescript" 10 | ], 11 | "scripts": { 12 | "dev": "NODE_ENV=development funblog lib dev", 13 | "build": "NODE_ENV=production funblog lib build" 14 | }, 15 | "license": "MIT", 16 | "dependencies": { 17 | "@funblog/cli": "workspace:*", 18 | "@funblog/eslint-config": "workspace:*", 19 | "@funblog/tsconfig": "workspace:*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 本地启动 2 | 3 | 1. 开启Mysql数据/ 创建.env.default中对应的账号密码 4 | 5 | 2. 初始化数据库 6 | - `pnpm run prisma:dev` 7 | 8 | 3. 启动项目 9 | - `pnpm run dev` 10 | 11 | - `http://localhost:8001` => server 12 | - `http://localhost:8002` => client 13 | - `http://localhost:8003` => admin 14 | 15 | ## docker启动 16 | 进入docker目录, 执行`docker compose --env-file ../.env.default up -d` 17 | 18 | 19 | ## docker 本地启动 20 | `pnpm run docker:compose` 21 | 22 | 23 | ## 数据初始化 24 | 25 | 26 | 1. 进入`http://localhost:8003/register` 基于.env.default中的邀请码进行账号注册 27 | 2. 登录之后进入`http://localhost:8003/init` 进行数据初始化 28 | 3. 访问 `http://localhost:8002` 便可展示初始数据 29 | 30 | 31 | -------------------------------------------------------------------------------- /apps/admin/src/middlewares/proxyMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, type NextRequest } from 'next/server'; 2 | import { METADATA } from '@/config/metaData'; 3 | import { MiddlewareFactory } from './types'; 4 | 5 | export const proxyMiddleware: MiddlewareFactory = (next) => { 6 | return async (request: NextRequest, _next) => { 7 | if (!/^\/api\/.*/.test(request.nextUrl.pathname)) { 8 | return next(request, _next); 9 | } 10 | const newUrl = 11 | request.nextUrl.pathname.replace(/^\/api/, `${METADATA.serverUrl}/api`) + (request.nextUrl.search || ''); 12 | return NextResponse.rewrite(newUrl); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /apps/client/src/middlewares/proxyMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, type NextRequest } from 'next/server'; 2 | import { METADATA } from '@/config/metaData'; 3 | import { MiddlewareFactory } from './types'; 4 | 5 | export const proxyMiddleware: MiddlewareFactory = (next) => { 6 | return async (request: NextRequest, _next) => { 7 | if (!/^\/api\/.*/.test(request.nextUrl.pathname)) { 8 | return next(request, _next); 9 | } 10 | const newUrl = 11 | request.nextUrl.pathname.replace(/^\/api/, `${METADATA.serverUrl}/api`) + (request.nextUrl.search || ''); 12 | return NextResponse.rewrite(newUrl); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /apps/server/src/utils/image-type.ts: -------------------------------------------------------------------------------- 1 | import { fromBuffer } from 'file-type'; 2 | 3 | const imageExtensions = new Set([ 4 | 'jpg', 5 | 'png', 6 | 'gif', 7 | 'webp', 8 | 'flif', 9 | 'cr2', 10 | 'tif', 11 | 'bmp', 12 | 'jxr', 13 | 'psd', 14 | 'ico', 15 | 'bpg', 16 | 'jp2', 17 | 'jpm', 18 | 'jpx', 19 | 'heic', 20 | 'cur', 21 | 'dcm', 22 | 'avif', 23 | ]); 24 | 25 | export default async function imageType(input) { 26 | const result = await fromBuffer(input); 27 | if (!result?.ext) return false; 28 | return imageExtensions.has(result?.ext) && result; 29 | } 30 | 31 | export const minimumBytes = 4100; 32 | -------------------------------------------------------------------------------- /packages/tsconfig/server.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "incremental": true, 12 | "skipLibCheck": true, 13 | "strictNullChecks": true, 14 | "noImplicitAny": false, 15 | "strictBindCallApply": false, 16 | "forceConsistentCasingInFileNames": false, 17 | "noFallthroughCasesInSwitch": false 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /apps/admin/src/api/visitor.ts: -------------------------------------------------------------------------------- 1 | import { Visitor, PageQuery, PageRes } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function getVisitorPage(params: PageQuery) { 5 | return request.get>('/api/visitor/page', { 6 | params, 7 | }); 8 | } 9 | 10 | export function getVisitorCount() { 11 | return request.get('/api/visitor/count'); 12 | } 13 | 14 | export function getVisitorSystem() { 15 | return request.get>('/api/visitor/system'); 16 | } 17 | 18 | export function getVisitorLocation() { 19 | return request.get>('/api/visitor/location'); 20 | } 21 | -------------------------------------------------------------------------------- /apps/admin/src/components/Table/index.tsx: -------------------------------------------------------------------------------- 1 | import { ParamsType, ProTable, ProTableProps } from '@ant-design/pro-components'; 2 | 3 | function Table, Params extends ParamsType = ParamsType, ValueType = 'text'>( 4 | props: ProTableProps, 5 | ) { 6 | return ( 7 | 18 | Table 19 | 20 | ); 21 | } 22 | 23 | export default Table; 24 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Size/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | function BannerSize({ className, children }: { className?: string; children: React.ReactNode }) { 3 | return ( 4 |
14 | {children} 15 |
16 | ); 17 | } 18 | 19 | export default BannerSize; 20 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.PageMenuType = void 0; 4 | (function (PageMenuType) { 5 | PageMenuType["NAVIGATION_NAV"] = "navigation-nav"; 6 | PageMenuType["SUB_NAV"] = "sub-nav"; 7 | })(exports.PageMenuType || (exports.PageMenuType = {})); 8 | exports.PageMenuButtonType = void 0; 9 | (function (PageMenuButtonType) { 10 | PageMenuButtonType["URL"] = "url"; 11 | PageMenuButtonType["CATEGORY"] = "category"; 12 | PageMenuButtonType["ARTICLE"] = "article"; 13 | PageMenuButtonType["TAG"] = "tag"; 14 | PageMenuButtonType["PAGE"] = "page"; 15 | })(exports.PageMenuButtonType || (exports.PageMenuButtonType = {})); 16 | -------------------------------------------------------------------------------- /packages/types/src/link.ts: -------------------------------------------------------------------------------- 1 | export enum LinkStatus { 2 | APPROVED = 'approved', 3 | PENDING = 'pending', 4 | REJECTED = 'rejected', 5 | } 6 | export enum LinkType { 7 | PERSONAL_BLOG = 'personal-Blog', 8 | WEBSITE_COMMUNITY = 'websiteCommunity', 9 | PERSONAL_ONLINE = 'personalOnline', 10 | PERSONAL_RECOMMENDATION = 'personalRecommendation', 11 | RESOURCE_MATERIALS = 'resourceMaterials', 12 | } 13 | 14 | export interface Link { 15 | id: number; 16 | title: string; 17 | desc: string; 18 | url: string; 19 | logo: string; 20 | type: LinkType; 21 | visible: boolean; 22 | status: LinkStatus; 23 | createdAt: Date; 24 | } 25 | -------------------------------------------------------------------------------- /apps/server/src/modules/tag/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsNotEmpty, IsOptional } from 'class-validator'; 3 | 4 | export class CreateTagDto { 5 | @IsNotEmpty({ 6 | message: '分类名称不能为空', 7 | }) 8 | name: string; 9 | 10 | @IsNotEmpty({ 11 | message: '分类别名不能为空', 12 | }) 13 | alias: string; 14 | } 15 | export class UpdateTagDto extends PartialType(CreateTagDto) {} 16 | 17 | export class BatchRemoveTagDto { 18 | @IsNotEmpty({ 19 | message: 'ID不能为空', 20 | }) 21 | ids: number[]; 22 | } 23 | 24 | export class QueryTagDto { 25 | @IsOptional() 26 | keyword: string; 27 | } 28 | -------------------------------------------------------------------------------- /apps/client/src/components/PanelArrow/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import React, { ReactNode } from 'react'; 3 | import styles from './style.module.css'; 4 | 5 | type Props = { 6 | title: string; 7 | children: ReactNode; 8 | className?: string; 9 | }; 10 | const PanelArrow = ({ children, title, className }: Props) => { 11 | return ( 12 |
13 |
14 |

{title}

15 |
16 |
{children}
17 |
18 | ); 19 | }; 20 | 21 | export default PanelArrow; 22 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Swiper/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@funblog/components'; 2 | import cls from 'classnames'; 3 | import styles from '../styles.module.css'; 4 | 5 | function Button({ className, onClick }: { className: string; onClick: () => void }) { 6 | return ( 7 | 17 | ); 18 | } 19 | 20 | export default Button; 21 | -------------------------------------------------------------------------------- /apps/server/src/config/metaData.ts: -------------------------------------------------------------------------------- 1 | import { SiteComment, SiteImage, SiteCommentReview, SiteEmail, SiteMeta, SiteUser } from '@funblog/types'; 2 | 3 | export interface MetaDataConfig { 4 | meta: Partial; 5 | image: Partial; 6 | comment: Partial; 7 | commentReview: Partial; 8 | email: Partial; 9 | user: Partial; 10 | } 11 | export const metaData = { 12 | config: {} as Partial, 13 | }; 14 | 15 | export const updateMetaDataConfig = (data: Partial) => { 16 | metaData.config = { 17 | ...metaData.config, 18 | ...data, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/types/lib/esm/link.js: -------------------------------------------------------------------------------- 1 | var LinkStatus; 2 | (function (LinkStatus) { 3 | LinkStatus["APPROVED"] = "approved"; 4 | LinkStatus["PENDING"] = "pending"; 5 | LinkStatus["REJECTED"] = "rejected"; 6 | })(LinkStatus || (LinkStatus = {})); 7 | var LinkType; 8 | (function (LinkType) { 9 | LinkType["PERSONAL_BLOG"] = "personal-Blog"; 10 | LinkType["WEBSITE_COMMUNITY"] = "websiteCommunity"; 11 | LinkType["PERSONAL_ONLINE"] = "personalOnline"; 12 | LinkType["PERSONAL_RECOMMENDATION"] = "personalRecommendation"; 13 | LinkType["RESOURCE_MATERIALS"] = "resourceMaterials"; 14 | })(LinkType || (LinkType = {})); 15 | 16 | export { LinkStatus, LinkType }; 17 | -------------------------------------------------------------------------------- /apps/client/src/hooks/useHash.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const getHash = () => 4 | typeof window !== 'undefined' ? decodeURIComponent(window.location.hash.replace('#', '')) : undefined; 5 | 6 | const useHash = () => { 7 | const [hash, setHash] = useState(getHash()); 8 | 9 | useEffect(() => { 10 | const handleHashChange = () => { 11 | setHash(getHash()); 12 | }; 13 | window.addEventListener('hashchange', handleHashChange); 14 | return () => { 15 | window.removeEventListener('hashchange', handleHashChange); 16 | }; 17 | }, []); 18 | 19 | return hash; 20 | }; 21 | 22 | export default useHash; 23 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/media/image/Card.tsx: -------------------------------------------------------------------------------- 1 | import { Image } from '@funblog/types'; 2 | import PreviewImage, { PreviewImageProps } from '@/components/PreviewImage'; 3 | import { getResourceUrl } from '@/utils'; 4 | 5 | function Card({ item, ...props }: { item: Image } & Pick) { 6 | return ( 7 |
8 | 15 |
16 | ); 17 | } 18 | 19 | export default Card; 20 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Banner/components/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import React from 'react'; 3 | 4 | const Card = React.forwardRef(function Card( 5 | { children, className }, 6 | ref, 7 | ) { 8 | return ( 9 |
17 | {children} 18 |
19 | ); 20 | }); 21 | 22 | export default Card; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | 12 | # next.js 13 | .next/ 14 | out/ 15 | build 16 | dist/ 17 | 18 | /github-test 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env 31 | .env.local 32 | 33 | /data 34 | # turbo 35 | .turbo 36 | 37 | # vercel 38 | .vercel 39 | 40 | # vscode 41 | .vscode 42 | 43 | 44 | .changeset/* 45 | !.changeset/config.json 46 | !.changeset/README.md 47 | 48 | 49 | docker/volumes -------------------------------------------------------------------------------- /apps/server/src/interceptors/globalResponseInterceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class GlobalResponseInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | // 在进入控制器之前执行的代码 9 | return next.handle().pipe( 10 | map((data) => { 11 | // 在控制器返回值之后执行的代码 12 | return { 13 | data, 14 | code: 0, 15 | message: 'success', 16 | }; 17 | }), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/server/src/modules/auth/strategies/local.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy } from 'passport-local'; 4 | import { AuthService } from '../auth.service'; 5 | 6 | @Injectable() 7 | export class LocalStrategy extends PassportStrategy(Strategy) { 8 | constructor(private authService: AuthService) { 9 | super({ 10 | usernameField: 'username', 11 | passwordField: 'password', 12 | }); 13 | } 14 | 15 | async validate(username: string, password: string): Promise { 16 | return await this.authService.validateUser(username, password); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tag'; 2 | export * from './category'; 3 | export * from './post'; 4 | export * from './site'; 5 | export * from './comment'; 6 | export * from './image'; 7 | export * from './userRole'; 8 | export * from './rolePermission'; 9 | export * from './invitationCode'; 10 | export * from './page'; 11 | export * from './visitor'; 12 | export * from './link'; 13 | export * from './svg'; 14 | 15 | export interface PageRes { 16 | list: T[]; 17 | total: number; 18 | } 19 | 20 | interface Query { 21 | page?: number; 22 | pageSize?: number; 23 | } 24 | export type PageQuery = T extends Record ? Query & T : Query; 25 | -------------------------------------------------------------------------------- /packages/types/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './tag'; 2 | export * from './category'; 3 | export * from './post'; 4 | export * from './site'; 5 | export * from './comment'; 6 | export * from './image'; 7 | export * from './userRole'; 8 | export * from './rolePermission'; 9 | export * from './invitationCode'; 10 | export * from './page'; 11 | export * from './visitor'; 12 | export * from './link'; 13 | export * from './svg'; 14 | export interface PageRes { 15 | list: T[]; 16 | total: number; 17 | } 18 | interface Query { 19 | page?: number; 20 | pageSize?: number; 21 | } 22 | export type PageQuery = T extends Record ? Query & T : Query; 23 | -------------------------------------------------------------------------------- /apps/admin/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @image (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /apps/server/src/modules/category/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsNotEmpty, IsOptional } from 'class-validator'; 3 | 4 | export class CreateCategoryDto { 5 | @IsNotEmpty({ 6 | message: '分类名称不能为空', 7 | }) 8 | name: string; 9 | 10 | @IsNotEmpty({ 11 | message: '分类别名不能为空', 12 | }) 13 | alias: string; 14 | } 15 | export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {} 16 | 17 | export class BatchRemoveCategoryDto { 18 | @IsNotEmpty({ 19 | message: '分类ID不能为空', 20 | }) 21 | ids: number[]; 22 | } 23 | 24 | export class QueryCategoryDto { 25 | @IsOptional() 26 | keyword: string; 27 | } 28 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/user.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { User } from 'src/modules/mongodb/types/user'; 3 | 4 | interface UserDocument extends db.Document, User { 5 | _id: string; 6 | } 7 | 8 | const UserModel = db.model( 9 | 'user', 10 | new db.Schema({ 11 | name: String, 12 | email: String, 13 | refId: String, 14 | avatar: String, 15 | url: String, 16 | role: { type: [String], default: [] }, 17 | ip: String, 18 | system: Object, 19 | location: Object, 20 | userAgent: String, 21 | createTime: { type: Date, default: Date.now }, 22 | }), 23 | ); 24 | 25 | export { UserModel, UserDocument }; 26 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/comment.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { Comment } from 'src/modules/mongodb/types/comment'; 3 | 4 | interface CommentDocument extends db.Document, Comment { 5 | _id: string; 6 | } 7 | 8 | const CommentModel = db.model( 9 | 'comment', 10 | new db.Schema({ 11 | replyList: { type: Array, default: [] }, 12 | userId: db.Schema.Types.ObjectId, 13 | name: String, 14 | url: String, 15 | avatar: String, 16 | content: String, 17 | floor: Number, 18 | role: [String], 19 | createTime: { type: Date, default: Date.now }, 20 | }), 21 | ); 22 | 23 | export { CommentModel, CommentDocument }; 24 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/Comment/components/Meta/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Icon } from '@funblog/components'; 3 | import BadgeRibbon from '@/components/BadgeRibbon'; 4 | import { useCommentStore } from '../../store'; 5 | 6 | function CommentMeta() { 7 | const { commentData } = useCommentStore(); 8 | return ( 9 |
10 | 11 |
12 | 13 | {commentData?.total || 0}条留言 14 |
15 |
16 |
17 | ); 18 | } 19 | 20 | export default CommentMeta; 21 | -------------------------------------------------------------------------------- /packages/types/types/link.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum LinkStatus { 2 | APPROVED = "approved", 3 | PENDING = "pending", 4 | REJECTED = "rejected" 5 | } 6 | export declare enum LinkType { 7 | PERSONAL_BLOG = "personal-Blog", 8 | WEBSITE_COMMUNITY = "websiteCommunity", 9 | PERSONAL_ONLINE = "personalOnline", 10 | PERSONAL_RECOMMENDATION = "personalRecommendation", 11 | RESOURCE_MATERIALS = "resourceMaterials" 12 | } 13 | export interface Link { 14 | id: number; 15 | title: string; 16 | desc: string; 17 | url: string; 18 | logo: string; 19 | type: LinkType; 20 | visible: boolean; 21 | status: LinkStatus; 22 | createdAt: Date; 23 | } 24 | -------------------------------------------------------------------------------- /apps/admin/src/api/comment.ts: -------------------------------------------------------------------------------- 1 | import { Comment, PageQuery, PageRes } from '@funblog/types'; 2 | import { request } from './fetch'; 3 | 4 | export function getCommentPage(params: PageQuery>>) { 5 | return request.get>('/api/comment/page', { 6 | params, 7 | }); 8 | } 9 | 10 | export function deleteComment(id: number) { 11 | return request.delete(`/api/comment/${id}`); 12 | } 13 | 14 | export function updateComment(id: number, body: Partial) { 15 | return request.patch(`/api/comment/${id}`, { 16 | body, 17 | }); 18 | } 19 | 20 | export function getCommentCount() { 21 | return request.get('/api/comment/count'); 22 | } 23 | -------------------------------------------------------------------------------- /packages/types/lib/cjs/link.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.LinkStatus = void 0; 4 | (function (LinkStatus) { 5 | LinkStatus["APPROVED"] = "approved"; 6 | LinkStatus["PENDING"] = "pending"; 7 | LinkStatus["REJECTED"] = "rejected"; 8 | })(exports.LinkStatus || (exports.LinkStatus = {})); 9 | exports.LinkType = void 0; 10 | (function (LinkType) { 11 | LinkType["PERSONAL_BLOG"] = "personal-Blog"; 12 | LinkType["WEBSITE_COMMUNITY"] = "websiteCommunity"; 13 | LinkType["PERSONAL_ONLINE"] = "personalOnline"; 14 | LinkType["PERSONAL_RECOMMENDATION"] = "personalRecommendation"; 15 | LinkType["RESOURCE_MATERIALS"] = "resourceMaterials"; 16 | })(exports.LinkType || (exports.LinkType = {})); 17 | -------------------------------------------------------------------------------- /apps/client/src/components/Loading/styles.module.css: -------------------------------------------------------------------------------- 1 | .loading div{ 2 | height: 30px; 3 | width: 4px; 4 | background: theme(colors.gray4); 5 | border-radius: 3px; 6 | display: inline-block; 7 | margin: 0 2px; 8 | box-shadow: 0 1px #fff; 9 | animation: 0.4s LineLoading ease-in infinite alternate; 10 | } 11 | .loading div:nth-child(1) { 12 | animation-delay: -0.3s; 13 | } 14 | 15 | .loading div:nth-child(2) { 16 | animation-delay: -0.2s; 17 | } 18 | 19 | .loading div:nth-child(3) { 20 | animation-delay: -0.1s; 21 | } 22 | 23 | 24 | 25 | @keyframes LineLoading { 26 | 0% { 27 | height: 30px; 28 | } 29 | 30 | 100% { 31 | height: 4px; 32 | opacity: 0.1; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/client/src/styles/utilities/background.css: -------------------------------------------------------------------------------- 1 | @layer utilities { 2 | .background-card { 3 | background: linear-gradient(#f4f7fa calc(100% - 1.5em), #e6ecf4); 4 | } 5 | .background-to-primary { 6 | background: linear-gradient(90deg, theme(colors.primary-light-1), theme(colors.primary)); 7 | } 8 | .background-to-primary-light { 9 | background: linear-gradient(90deg, theme(colors.primary), theme(colors.primary-light-1)); 10 | } 11 | .background-to-gray { 12 | background: linear-gradient(90deg, theme(colors.gray7), theme(colors.gray6), theme(colors.gray7)); 13 | } 14 | 15 | .background-input { 16 | background: linear-gradient(#f0f2f7, theme(colors.white)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/server/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req } from '@nestjs/common'; 2 | import { Request } from 'express'; 3 | import { AppService } from './app.service'; 4 | import { Public } from './decorators/public'; 5 | 6 | @Public() 7 | @Controller() 8 | export class AppController { 9 | constructor(private readonly appService: AppService) {} 10 | 11 | @Get() 12 | getHello(): string { 13 | return this.appService.getHello(); 14 | } 15 | 16 | @Get('health-check') 17 | healthCheck() { 18 | return 'ok'; 19 | } 20 | 21 | @Get('send') 22 | send(@Req() req: Request) { 23 | console.log(111, req.headers['user-agent'] || ''); 24 | return this.appService.send(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS base 2 | RUN corepack enable 3 | RUN apk add --no-cache curl 4 | COPY . /app 5 | WORKDIR /app 6 | 7 | # RUN apk add chromium 8 | 9 | # ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true 10 | # ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser 11 | 12 | COPY ./opts/package.json /app 13 | 14 | FROM base AS deps 15 | RUN pnpm install 16 | 17 | FROM deps AS build 18 | COPY --from=deps /app/node_modules /app/node_modules 19 | RUN pnpm run build 20 | 21 | FROM base 22 | COPY --from=build /app/node_modules /app/node_modules 23 | COPY --from=build /app/dist /app/dist 24 | 25 | EXPOSE 8020 26 | CMD ["sh", "-c", "pnpm run prisma:init && pnpm run prisma:deploy && pnpm run start"] 27 | -------------------------------------------------------------------------------- /apps/server/src/modules/image/dto/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | import { PaginationDto } from 'src/dtos/pagination.dto'; 4 | 5 | export class CreateImageDto { 6 | userId: number; 7 | originalname: string; 8 | filename: string; 9 | originalUrl: string; 10 | url: string; 11 | thumbnailUrl?: string; 12 | metadata: Record; 13 | } 14 | export class UpdateImageDto extends PartialType(CreateImageDto) {} 15 | 16 | export class UpdateImageFilenameDto { 17 | @IsNotEmpty({ 18 | message: '文件名不能为空', 19 | }) 20 | filename: string; 21 | } 22 | 23 | export class ImagePaginationDto extends PaginationDto {} 24 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/DndKit/Draggable.tsx: -------------------------------------------------------------------------------- 1 | import { useDraggable } from '@dnd-kit/core'; 2 | import React from 'react'; 3 | 4 | export function Draggable(props: any) { 5 | const { attributes, listeners, setNodeRef, transform } = useDraggable({ 6 | id: props.id, 7 | }); 8 | const style = transform 9 | ? { 10 | transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, 11 | } 12 | : undefined; 13 | return ( 14 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/admin/src/components/Layout/style.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | :global { 3 | .ant-page-header-breadcrumb { 4 | padding-top: 0 !important; 5 | } 6 | .ant-page-header-no-children { 7 | display: none; 8 | } 9 | .ant-page-header { 10 | padding-left: 24px; 11 | padding-right: 24px; 12 | } 13 | .ant-pro-page-container-children-container { 14 | padding-left: 24px; 15 | padding-right: 24px; 16 | padding-bottom: 24px; 17 | } 18 | } 19 | 20 | &.full { 21 | :global { 22 | .ant-page-header { 23 | display: none; 24 | } 25 | .ant-pro-page-container-children-container { 26 | padding: 0; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/client/src/proComponents/PostCard/ColCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .card { 2 | &:hover { 3 | transform: translateY(1em); 4 | box-shadow: 0 0.2em 0.4em theme(colors.gray/0.1); 5 | .cover { 6 | transform: translateY(1em); 7 | .coverBg { 8 | opacity: 0; 9 | } 10 | .date { 11 | opacity: 1; 12 | } 13 | } 14 | .title { 15 | transform: translateY(0.5em); 16 | } 17 | .meta { 18 | transform: translateY(0.8em) scale(0.8); 19 | opacity: 0; 20 | } 21 | .tags { 22 | opacity: 1; 23 | visibility: visible; 24 | transform: scale(1); 25 | } 26 | .button { 27 | transform: translateY(0.3em); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/github.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { Github } from 'src/modules/mongodb/types/github'; 3 | 4 | interface GithubDocument extends db.Document, Github { 5 | _id: string; 6 | } 7 | 8 | const GithubModel = db.model( 9 | 'github', 10 | new db.Schema({ 11 | name: String, 12 | isVisible: Boolean, 13 | stargazers_count: Number, 14 | forks_count: Number, 15 | subscribers_count: Number, 16 | description: String, 17 | html_url: String, 18 | created_at: String, 19 | updated_at: String, 20 | seq: Number, 21 | createTime: { type: Date, default: Date.now }, 22 | }), 23 | ); 24 | 25 | export { GithubModel, GithubDocument }; 26 | -------------------------------------------------------------------------------- /packages/hooks/src/useResize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { debounceFn } from './utils'; 3 | 4 | export default function useResize({ 5 | debounce, 6 | delay, 7 | callback, 8 | }: { 9 | debounce?: boolean; 10 | delay?: number; 11 | callback: (e?: UIEvent) => void; 12 | }) { 13 | const ref = useRef(callback); 14 | ref.current = callback; 15 | useEffect(() => { 16 | const _fn = (e?: UIEvent) => { 17 | ref.current(e); 18 | }; 19 | const fn = debounce ? debounceFn(_fn, delay) : _fn; 20 | fn(); 21 | window.addEventListener('resize', fn); 22 | return () => { 23 | window.removeEventListener('resize', fn); 24 | }; 25 | }, [debounce, delay]); 26 | } 27 | -------------------------------------------------------------------------------- /packages/tsconfig/base.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "inlineSources": false, 10 | "isolatedModules": true, 11 | "incremental": true, 12 | "noEmit": true, 13 | "moduleResolution": "bundler", 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "preserveWatchOutput": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "strictNullChecks": true 21 | }, 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/admin/src/app/(noauth)/test/Tree/Item/components/Handle/Handle.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | 3 | import { Action, ActionProps } from '../Action'; 4 | 5 | export const Handle = forwardRef((props, ref) => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/server/src/modules/auth/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { ExtractJwt, Strategy } from 'passport-jwt'; 5 | 6 | @Injectable() 7 | export class JwtStrategy extends PassportStrategy(Strategy) { 8 | constructor(protected readonly configService: ConfigService) { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 11 | ignoreExpiration: false, 12 | secretOrKey: configService.get('jwt.secret'), 13 | }); 14 | } 15 | 16 | async validate(payload) { 17 | return { 18 | ...payload, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/page/menu/style.module.css: -------------------------------------------------------------------------------- 1 | .menu { 2 | :global { 3 | .ant-collapse-content-box { 4 | padding-top: 0!important; 5 | padding-bottom: 0!important; 6 | } 7 | .ant-form-item { 8 | margin-bottom: 12px; 9 | } 10 | .ant-form-item-label { 11 | label { 12 | color: #475669; 13 | } 14 | } 15 | .ant-list-item { 16 | border: none; 17 | padding: 2px 0; 18 | } 19 | .ant-list-item-meta-title { 20 | color: #475669!important; 21 | font-weight: normal; 22 | } 23 | } 24 | } 25 | 26 | .content { 27 | :global { 28 | .ant-collapse-content { 29 | border-top: 1px solid theme(colors.gray7)!important; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /apps/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/client/src/components/Popup/styles.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | opacity: 0; 3 | } 4 | .arrow, 5 | .arrow::before { 6 | position: absolute; 7 | width: 8px; 8 | height: 8px; 9 | background: inherit; 10 | z-index: -1; 11 | } 12 | 13 | .arrow { 14 | visibility: hidden; 15 | } 16 | 17 | .arrow::before { 18 | visibility: visible; 19 | content: ''; 20 | transform: rotate(45deg); 21 | } 22 | 23 | .popup[data-popper-placement^='top'] .arrow { 24 | bottom: -4px; 25 | } 26 | 27 | .popup[data-popper-placement^='bottom'] .arrow { 28 | top: -4px; 29 | } 30 | 31 | .popup[data-popper-placement^='left'] .arrow { 32 | right: -4px; 33 | } 34 | 35 | .popup[data-popper-placement^='right'] .arrow { 36 | left: -4px; 37 | } 38 | -------------------------------------------------------------------------------- /apps/admin/src/app/(auth)/dashboard/echarts/utils.ts: -------------------------------------------------------------------------------- 1 | // 解析system 2 | export const parseObj = (obj: Record) => { 3 | const names = Object.keys(obj); 4 | return names 5 | .map((name) => ({ 6 | name, 7 | value: obj[name], 8 | })) 9 | .sort((a, b) => b.value - a.value); 10 | }; 11 | 12 | // 解析data 13 | export const parseData = (data: { name: string; value: any }[]) => { 14 | return data.reduce( 15 | (obj, item) => { 16 | obj.legendData.push(item.name); 17 | obj.seriesData.push(item); 18 | return obj; 19 | }, 20 | { 21 | legendData: [], 22 | seriesData: [], 23 | } as { 24 | legendData: string[]; 25 | seriesData: any[]; 26 | }, 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /apps/server/src/modules/mongodb/models/play.ts: -------------------------------------------------------------------------------- 1 | import db from 'mongoose'; 2 | import { Play } from 'src/modules/mongodb/types/play'; 3 | 4 | interface PlayDocument extends db.Document, Play { 5 | _id: string; 6 | } 7 | 8 | const PlayModel = db.model( 9 | 'play', 10 | new db.Schema({ 11 | title: String, 12 | tags: Array, 13 | desc: String, 14 | github: String, 15 | cover: String, 16 | fileType: String, 17 | file: String, 18 | folder: [String], 19 | url: String, 20 | source: String, 21 | download_nums: { type: Number, default: 0 }, 22 | isVisible: Boolean, 23 | createTime: { type: Date, default: Date.now }, 24 | }), 25 | ); 26 | 27 | export { PlayModel, PlayDocument }; 28 | -------------------------------------------------------------------------------- /apps/server/src/modules/svg/svg.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from '@nestjs/common'; 2 | import { Public } from 'src/decorators/public'; 3 | import { CreateSvgDto } from './dto/request.dto'; 4 | import { SvgService } from './svg.service'; 5 | 6 | @Public() 7 | @Controller('p/svg') 8 | export class SvgPublicController { 9 | constructor(private readonly svgService: SvgService) {} 10 | 11 | @Post() 12 | create(@Body() body: CreateSvgDto) { 13 | return this.svgService.create(body); 14 | } 15 | 16 | @Get('list') 17 | list() { 18 | return this.svgService.getClientList(); 19 | } 20 | 21 | @Get('list/admin') 22 | adminList() { 23 | return this.svgService.getAdminList(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/admin/src/components/FormUpload/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form } from 'antd'; 2 | import { FormItemProps } from 'antd/lib'; 3 | import Upload, { UploadProps } from '@/components/Upload'; 4 | 5 | type FieldProps = Partial & { 6 | onChange?: (value: string | string[]) => void; 7 | }; 8 | 9 | function IUpload({ value, onChange, ...props }: FieldProps) { 10 | return ; 11 | } 12 | 13 | function FormUpload({ 14 | fieldProps, 15 | ...props 16 | }: Omit & { 17 | fieldProps?: FieldProps; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | export default FormUpload; 27 | -------------------------------------------------------------------------------- /packages/utils/src/getOS.ts: -------------------------------------------------------------------------------- 1 | // 获取浏览器类型 2 | export default function getOS(ua: string) { 3 | const isWindowsPhone = /(?:Windows Phone)/.test(ua); 4 | const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone; 5 | const isFireFox = /(?:Firefox)/.test(ua); 6 | const isChrome = /(?:Chrome|CriOS)/.test(ua); 7 | const isAndroid = /(?:Android)/.test(ua); 8 | const isTablet = 9 | /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)); 10 | const isPhone = /(?:iPhone)/.test(ua) && !isTablet; 11 | const isPc = !isPhone && !isAndroid && !isSymbian; 12 | return { 13 | isChrome, 14 | isFireFox, 15 | isTablet, 16 | isPhone, 17 | isAndroid, 18 | isPc, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/cli/lib/replaceWorkspace.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { createExportableManifest } = require('@pnpm/exportable-manifest'); 4 | const { readProjectManifestOnly } = require('@pnpm/read-project-manifest'); 5 | 6 | exports.replaceWorkspace = async () => { 7 | const projectDir = process.cwd(); 8 | const outputDir = path.resolve(projectDir, 'opts'); 9 | const manifest = await readProjectManifestOnly(projectDir); 10 | const exportable = await createExportableManifest(projectDir, manifest); 11 | if (!fs.existsSync(outputDir)) { 12 | fs.mkdirSync(outputDir, { recursive: true }); 13 | } 14 | fs.writeFileSync(path.resolve(outputDir, 'package.json'), JSON.stringify(exportable, undefined, 2)); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/components/src/components/Portal/index.tsx: -------------------------------------------------------------------------------- 1 | import { canUseDom } from '@funblog/utils'; 2 | import { useRef } from 'react'; 3 | import ReactDom from 'react-dom'; 4 | 5 | type Props = { 6 | children: any; 7 | container?: any; 8 | }; 9 | interface PortalProps { 10 | (props: Props): JSX.Element | null; 11 | } 12 | 13 | const Portal: PortalProps = ({ children, container }) => { 14 | const containerRef = useRef(); 15 | if (canUseDom()) { 16 | if (!container) { 17 | containerRef.current = document.body; 18 | } else { 19 | containerRef.current = container; 20 | } 21 | } 22 | return containerRef.current ? ReactDom.createPortal(children, containerRef.current) : null; 23 | }; 24 | 25 | export default Portal; 26 | --------------------------------------------------------------------------------