├── .devcontainer
├── Dockerfile
├── devcontainer.json
└── docker-compose.yml
├── .editorconfig
├── .env.example
├── .eslintrc.json
├── .github
├── renovate.json
└── workflows
│ └── lint-check.yml
├── .gitignore
├── .idx
└── dev.nix
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── app
├── about
│ ├── [...slug]
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── metadata.ts
│ └── page.tsx
├── actions.ts
├── admin
│ ├── comment
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── creator
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── galgame
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── log
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── metadata.ts
│ ├── page.tsx
│ ├── resource
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── setting
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ └── user
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
├── api
│ ├── admin
│ │ ├── comment
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ │ ├── creator
│ │ │ ├── approve
│ │ │ │ └── route.ts
│ │ │ ├── decline
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── galgame
│ │ │ └── route.ts
│ │ ├── log
│ │ │ └── route.ts
│ │ ├── resource
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ │ ├── setting
│ │ │ ├── comment
│ │ │ │ ├── getCommentVerifyStatus.ts
│ │ │ │ └── route.ts
│ │ │ ├── creator
│ │ │ │ ├── getEnableOnlyCreatorCreateStatus.ts
│ │ │ │ └── route.ts
│ │ │ └── register
│ │ │ │ └── route.ts
│ │ ├── stats
│ │ │ ├── route.ts
│ │ │ └── sum
│ │ │ │ └── route.ts
│ │ └── user
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ ├── apply
│ │ ├── route.ts
│ │ └── status
│ │ │ └── route.ts
│ ├── auth
│ │ ├── captcha
│ │ │ ├── _utils.ts
│ │ │ ├── generate.ts
│ │ │ └── route.ts
│ │ ├── login
│ │ │ └── route.ts
│ │ ├── register
│ │ │ └── route.ts
│ │ └── send-register-code
│ │ │ └── route.ts
│ ├── comment
│ │ └── route.ts
│ ├── company
│ │ ├── all
│ │ │ └── route.ts
│ │ ├── galgame
│ │ │ └── route.ts
│ │ ├── route.ts
│ │ ├── search
│ │ │ └── route.ts
│ │ └── upload-logo
│ │ │ └── route.ts
│ ├── edit
│ │ ├── _helpers.ts
│ │ ├── create.ts
│ │ ├── duplicate
│ │ │ └── route.ts
│ │ ├── route.ts
│ │ └── update.ts
│ ├── forgot
│ │ ├── one
│ │ │ └── route.ts
│ │ └── two
│ │ │ └── route.ts
│ ├── galgame
│ │ └── route.ts
│ ├── hikari
│ │ ├── route.ts
│ │ └── type.d.ts
│ ├── home
│ │ └── route.ts
│ ├── message
│ │ ├── all
│ │ │ └── route.ts
│ │ ├── read
│ │ │ └── route.ts
│ │ ├── route.ts
│ │ └── unread
│ │ │ └── route.ts
│ ├── patch
│ │ ├── banner
│ │ │ └── route.ts
│ │ ├── comment
│ │ │ ├── _helpers.ts
│ │ │ ├── create.ts
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── like
│ │ │ │ └── route.ts
│ │ │ ├── markdown
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ │ ├── contributor
│ │ │ └── route.ts
│ │ ├── delete.ts
│ │ ├── get.ts
│ │ ├── history
│ │ │ └── route.ts
│ │ ├── introduction
│ │ │ ├── company
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ └── tag
│ │ │ │ └── route.ts
│ │ ├── like
│ │ │ └── route.ts
│ │ ├── pr
│ │ │ ├── decline
│ │ │ │ └── route.ts
│ │ │ ├── merge
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── resource
│ │ │ ├── _helper.ts
│ │ │ ├── create.ts
│ │ │ ├── delete.ts
│ │ │ ├── download
│ │ │ │ └── route.ts
│ │ │ ├── get.ts
│ │ │ ├── like
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ │ ├── route.ts
│ │ └── walkthrough
│ │ │ ├── create.ts
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── route.ts
│ │ │ └── update.ts
│ ├── release
│ │ └── route.ts
│ ├── resource
│ │ └── route.ts
│ ├── search
│ │ └── route.ts
│ ├── tag
│ │ ├── all
│ │ │ └── route.ts
│ │ ├── galgame
│ │ │ └── route.ts
│ │ ├── route.ts
│ │ └── search
│ │ │ └── route.ts
│ ├── upload
│ │ ├── calculateFileStreamHash.ts
│ │ ├── resource
│ │ │ └── route.ts
│ │ └── utils.ts
│ ├── user
│ │ ├── follow
│ │ │ ├── follow
│ │ │ │ └── route.ts
│ │ │ ├── follower
│ │ │ │ └── route.ts
│ │ │ ├── following
│ │ │ │ └── route.ts
│ │ │ └── unfollow
│ │ │ │ └── route.ts
│ │ ├── image
│ │ │ ├── _upload.ts
│ │ │ └── route.ts
│ │ ├── mention
│ │ │ └── search
│ │ │ │ └── route.ts
│ │ ├── profile
│ │ │ ├── comment
│ │ │ │ └── route.ts
│ │ │ ├── contribute
│ │ │ │ └── route.ts
│ │ │ ├── favorite
│ │ │ │ └── route.ts
│ │ │ ├── floating
│ │ │ │ └── route.ts
│ │ │ ├── galgame
│ │ │ │ └── route.ts
│ │ │ └── resource
│ │ │ │ └── route.ts
│ │ ├── setting
│ │ │ ├── _upload.ts
│ │ │ ├── avatar
│ │ │ │ └── route.ts
│ │ │ ├── bio
│ │ │ │ └── route.ts
│ │ │ ├── email
│ │ │ │ └── route.ts
│ │ │ ├── password
│ │ │ │ └── route.ts
│ │ │ ├── send-reset-email-code
│ │ │ │ └── route.ts
│ │ │ └── username
│ │ │ │ └── route.ts
│ │ └── status
│ │ │ ├── check-in
│ │ │ └── route.ts
│ │ │ ├── info
│ │ │ └── route.ts
│ │ │ ├── logout
│ │ │ └── route.ts
│ │ │ └── route.ts
│ └── utils
│ │ ├── algorithm.ts
│ │ ├── checkBufferSize.ts
│ │ ├── constants.ts
│ │ ├── createMentionMessage.ts
│ │ ├── generateRandomCode.ts
│ │ ├── getNSFWHeader.ts
│ │ ├── getRemoteIp.ts
│ │ ├── jwt.ts
│ │ ├── markdownToHtml.ts
│ │ ├── message.ts
│ │ ├── parseQuery.ts
│ │ ├── sendVerificationCodeEmail.ts
│ │ ├── uploadPatchBanner.ts
│ │ ├── verifyKunCaptcha.ts
│ │ └── verifyVerificationCode.ts
├── apply
│ ├── actions.ts
│ ├── metadata.ts
│ ├── page.tsx
│ ├── pending
│ │ ├── metadata.ts
│ │ └── page.tsx
│ └── success
│ │ ├── metadata.ts
│ │ └── page.tsx
├── auth
│ └── forgot
│ │ ├── metadata.ts
│ │ └── page.tsx
├── check-hash
│ ├── metadata.ts
│ └── page.tsx
├── comment
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
├── company
│ ├── [id]
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
├── edit
│ ├── create
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── page.tsx
│ └── rewrite
│ │ ├── metadata.ts
│ │ └── page.tsx
├── error.tsx
├── friend-link
│ ├── metadata.ts
│ └── page.tsx
├── galgame
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
├── layout.tsx
├── login
│ ├── metadata.ts
│ └── page.tsx
├── message
│ ├── actions.ts
│ ├── follow
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── mention
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── metadata.ts
│ ├── notice
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── page.tsx
│ ├── patch-resource-create
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── patch-resource-update
│ │ ├── metadata.ts
│ │ └── page.tsx
│ └── system
│ │ ├── metadata.ts
│ │ └── page.tsx
├── metadata.ts
├── page.tsx
├── patch
│ └── [id]
│ │ ├── actions.ts
│ │ ├── comment
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ │ ├── history
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ │ ├── introduction
│ │ ├── metadata.ts
│ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── metadata.ts
│ │ ├── page.tsx
│ │ ├── pr
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ │ ├── resource
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ │ └── walkthrough
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
├── providers.tsx
├── ranking
│ ├── actions.ts
│ ├── page.tsx
│ ├── patch
│ │ ├── metadata.ts
│ │ └── page.tsx
│ └── user
│ │ ├── metadata.ts
│ │ └── page.tsx
├── register
│ ├── metadata.ts
│ └── page.tsx
├── release
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
├── resource
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
├── search
│ ├── metadata.ts
│ └── page.tsx
├── settings
│ └── user
│ │ ├── metadata.ts
│ │ └── page.tsx
├── tag
│ ├── [id]
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── actions.ts
│ ├── metadata.ts
│ └── page.tsx
└── user
│ ├── [id]
│ ├── actions.ts
│ ├── comment
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── contribute
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── favorite
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── galgame
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── metadata.ts
│ ├── page.tsx
│ └── resource
│ │ ├── actions.ts
│ │ ├── metadata.ts
│ │ └── page.tsx
│ └── page.tsx
├── components
├── about
│ ├── BlogHeader.tsx
│ ├── Card.tsx
│ ├── Header.tsx
│ ├── Navigation.tsx
│ ├── SideTreeItem.tsx
│ ├── Sidebar.tsx
│ ├── SidebarContent.tsx
│ └── TableOfContents.tsx
├── admin
│ ├── Navbar.tsx
│ ├── Sidebar.tsx
│ ├── SidebarContent.tsx
│ ├── comment
│ │ ├── Card.tsx
│ │ ├── CommentEdit.tsx
│ │ └── Container.tsx
│ ├── creator
│ │ ├── ActionButton.tsx
│ │ ├── Container.tsx
│ │ └── RenderCell.tsx
│ ├── galgame
│ │ ├── Container.tsx
│ │ └── RenderCell.tsx
│ ├── log
│ │ ├── Card.tsx
│ │ └── Container.tsx
│ ├── resource
│ │ ├── Container.tsx
│ │ ├── RenderCell.tsx
│ │ └── ResourceEdit.tsx
│ ├── setting
│ │ ├── Container.tsx
│ │ ├── DisableRegisterSetting.tsx
│ │ ├── EnableCommentVerify.tsx
│ │ └── EnableOnlyCreatorCreateGalgame.tsx
│ ├── stats
│ │ ├── KunAdminStatistic.tsx
│ │ ├── KunAdminSum.tsx
│ │ ├── KunStats.tsx
│ │ └── StatsCard.tsx
│ └── user
│ │ ├── Container.tsx
│ │ ├── RenderCell.tsx
│ │ ├── UserDelete.tsx
│ │ └── UserEdit.tsx
├── apply
│ ├── Container.tsx
│ └── Success.tsx
├── check-hash
│ ├── CheckHash.tsx
│ └── Container.tsx
├── comment
│ ├── CommentCard.tsx
│ ├── Container.tsx
│ ├── FilterBar.tsx
│ └── _sort.d.ts
├── company
│ ├── Card.tsx
│ ├── CompanyHeader.tsx
│ ├── CompanyList.tsx
│ ├── Container.tsx
│ ├── SearchCompanies.tsx
│ ├── detail
│ │ └── Container.tsx
│ └── form
│ │ ├── ArrayAdder.tsx
│ │ ├── CompanyFormModal.tsx
│ │ └── LogoImage.tsx
├── edit
│ ├── VNDB.d.ts
│ ├── components
│ │ └── ReleaseDateInput.tsx
│ ├── create
│ │ ├── AliasInput.tsx
│ │ ├── BannerImage.tsx
│ │ ├── ContentLimit.tsx
│ │ ├── CreatePatch.tsx
│ │ ├── PatchIntroduction.tsx
│ │ ├── PublishButton.tsx
│ │ └── VNDBInput.tsx
│ └── rewrite
│ │ ├── AliasManager.tsx
│ │ ├── ContentLimit.tsx
│ │ ├── GameNameInput.tsx
│ │ ├── RewritePatch.tsx
│ │ ├── RewritePatchBanner.tsx
│ │ └── VNDBInput.tsx
├── error
│ └── ErrorComponent.tsx
├── forgot
│ ├── Forgot.tsx
│ ├── StepOne.tsx
│ └── StepTwo.tsx
├── friend-link
│ └── KunFriendLink.tsx
├── galgame
│ ├── Card.tsx
│ ├── Container.tsx
│ ├── FilterBar.tsx
│ └── _sort.d.ts
├── home
│ ├── Container.tsx
│ ├── Hero.tsx
│ ├── PatchCard.tsx
│ └── carousel
│ │ ├── DesktopCard.tsx
│ │ ├── KunCarousel.tsx
│ │ ├── KunMobileCard.tsx
│ │ ├── KunNavigationMenu.tsx
│ │ └── mdx.ts
├── kun
│ ├── BackToTop.tsx
│ ├── CardStats.tsx
│ ├── Footer.tsx
│ ├── Header.tsx
│ ├── Loading.tsx
│ ├── MasonryGrid.tsx
│ ├── NSFWIndicator.tsx
│ ├── NavigationBreadcrumb.tsx
│ ├── Null.tsx
│ ├── PatchAttribute.tsx
│ ├── Sooner.tsx
│ ├── TextDivider.tsx
│ ├── auth
│ │ ├── CaptchaCanvas.tsx
│ │ ├── CaptchaModal.tsx
│ │ └── captcha.d.ts
│ ├── cropper
│ │ ├── KunCropControls.tsx
│ │ ├── KunImageCropper.tsx
│ │ ├── KunImageCropperModal.tsx
│ │ ├── KunImageMosaicModal.tsx
│ │ ├── KunImageUploader.tsx
│ │ ├── KunMosaicController.tsx
│ │ ├── types.d.ts
│ │ └── utils.ts
│ ├── floating-card
│ │ ├── KunAvatar.tsx
│ │ ├── KunUser.tsx
│ │ ├── KunUserCard.tsx
│ │ └── KunUserStatCard.tsx
│ ├── icons
│ │ ├── GitHub.tsx
│ │ ├── Markdown.tsx
│ │ ├── Microsoft.tsx
│ │ └── Telegram.tsx
│ ├── image-viewer
│ │ ├── AutoImageViewer.tsx
│ │ └── ImageViewer.tsx
│ ├── milkdown
│ │ ├── Editor.tsx
│ │ ├── EditorProvider.tsx
│ │ ├── PatchEditor.tsx
│ │ └── plugins
│ │ │ ├── Menu.tsx
│ │ │ ├── MenuButton.tsx
│ │ │ ├── _buttonList.ts
│ │ │ ├── emoji
│ │ │ ├── EmojiPicker.tsx
│ │ │ └── _isoEmoji.ts
│ │ │ ├── mention
│ │ │ └── MentionsListDropdown.tsx
│ │ │ ├── placeholder
│ │ │ └── placeholderPlugin.ts
│ │ │ ├── stop-link
│ │ │ └── stopLinkPlugin.ts
│ │ │ └── uploader.ts
│ ├── top-bar
│ │ ├── Brand.tsx
│ │ ├── KunMobileMenu.tsx
│ │ ├── NSFWSwitcher.tsx
│ │ ├── Search.tsx
│ │ ├── ThemeSwitcher.tsx
│ │ ├── TopBar.tsx
│ │ ├── User.tsx
│ │ ├── UserDropdown.tsx
│ │ ├── UserMessageBell.tsx
│ │ └── user
│ │ │ ├── CheckIn.tsx
│ │ │ └── Logout.tsx
│ ├── utils
│ │ └── loli.ts
│ └── verification-code
│ │ └── Code.tsx
├── login
│ └── Login.tsx
├── message
│ ├── Container.tsx
│ ├── MessageCard.tsx
│ └── MessageNav.tsx
├── patch
│ ├── Container.tsx
│ ├── Contributor.tsx
│ ├── DiffContent.tsx
│ ├── _diff.scss
│ ├── comment
│ │ ├── CommentContent.tsx
│ │ ├── CommentDropdown.tsx
│ │ ├── CommentLike.tsx
│ │ ├── Comments.tsx
│ │ ├── PublishComment.tsx
│ │ └── _scrollIntoComment.ts
│ ├── header
│ │ ├── Actions.tsx
│ │ ├── BackgroundImage.tsx
│ │ ├── Container.tsx
│ │ ├── EditBanner.tsx
│ │ ├── Info.tsx
│ │ ├── Tabs.tsx
│ │ └── Tags.tsx
│ ├── history
│ │ ├── Card.tsx
│ │ └── History.tsx
│ ├── introduction
│ │ ├── Company.tsx
│ │ ├── Container.tsx
│ │ ├── Info.tsx
│ │ ├── PatchCompanySelector.tsx
│ │ ├── PatchTagSelector.tsx
│ │ └── Tag.tsx
│ ├── pr
│ │ └── PullRequest.tsx
│ ├── resource
│ │ ├── DownloadCard.tsx
│ │ ├── Resource.tsx
│ │ ├── ResourceDownload.tsx
│ │ ├── ResourceInfo.tsx
│ │ ├── ResourceLike.tsx
│ │ ├── edit
│ │ │ └── EditResourceDialog.tsx
│ │ ├── publish
│ │ │ ├── PublishResource.tsx
│ │ │ ├── ResourceDetailsForm.tsx
│ │ │ ├── ResourceLinksInput.tsx
│ │ │ └── ResourceTypeSelect.tsx
│ │ ├── share.d.ts
│ │ └── upload
│ │ │ ├── FileDropZone.tsx
│ │ │ ├── FileUploadCard.tsx
│ │ │ ├── FileUploadContainer.tsx
│ │ │ └── utils.ts
│ └── walkthrough
│ │ ├── Card.tsx
│ │ ├── Container.tsx
│ │ ├── PublishWalkthrough.tsx
│ │ └── WalkthroughDropdown.tsx
├── ranking
│ ├── Container.tsx
│ ├── patch
│ │ └── PatchList.tsx
│ └── user
│ │ ├── UserCard.tsx
│ │ ├── UserList.tsx
│ │ └── UserStatsItem.tsx
├── register
│ └── Register.tsx
├── release
│ ├── Container.tsx
│ ├── MonthNavigation.tsx
│ └── ReleaseCard.tsx
├── resource
│ ├── Container.tsx
│ ├── FilterBar.tsx
│ ├── ResourceCard.tsx
│ └── _sort.d.ts
├── search
│ ├── Container.tsx
│ └── SearchHistory.tsx
├── settings
│ ├── Nav.tsx
│ └── user
│ │ ├── Avatar.tsx
│ │ ├── AvatarCrop.tsx
│ │ ├── Bio.tsx
│ │ ├── Container.tsx
│ │ ├── Email.tsx
│ │ ├── MessageSettings.tsx
│ │ ├── Password.tsx
│ │ ├── Reset.tsx
│ │ └── Username.tsx
├── tag
│ ├── Card.tsx
│ ├── Container.tsx
│ ├── CreateTagModal.tsx
│ ├── SearchTag.tsx
│ ├── TagHeader.tsx
│ ├── TagList.tsx
│ └── detail
│ │ ├── Container.tsx
│ │ └── EditTagModal.tsx
└── user
│ ├── Activity.tsx
│ ├── Profile.tsx
│ ├── SelfButton.tsx
│ ├── Stats.tsx
│ ├── comment
│ ├── Card.tsx
│ └── Container.tsx
│ ├── contribute
│ ├── Card.tsx
│ └── Container.tsx
│ ├── favorite
│ └── Container.tsx
│ ├── follow
│ ├── Follow.tsx
│ ├── Stats.tsx
│ └── UserList.tsx
│ ├── galgame
│ ├── Card.tsx
│ └── Container.tsx
│ └── resource
│ ├── Card.tsx
│ └── Container.tsx
├── config
├── config.d.ts
├── email-whitelist.ts
├── friend.json
├── friend.ts
├── moyu-moe.ts
├── redis.ts
├── search.ts
├── upload.ts
└── user.ts
├── constants
├── about.ts
├── admin.ts
├── api
│ └── select.ts
├── captcha.ts
├── company.ts
├── galgame.ts
├── history.ts
├── home-typed-js.ts
├── message.ts
├── ranking.tsx
├── resource.ts
├── routes
│ ├── constants.ts
│ ├── matcher.ts
│ └── routes.ts
├── top-bar.ts
└── user.ts
├── ecosystem.config.js
├── hooks
├── useConfetti.ts
├── useMounted.ts
├── useResizeObserver.ts
└── useWindowSize.ts
├── instrumentation.ts
├── lib
├── mdx
│ ├── CustomMDX.tsx
│ ├── directoryTree.ts
│ ├── element
│ │ ├── KunCode.tsx
│ │ ├── KunLink.tsx
│ │ ├── KunTable.tsx
│ │ └── kunHeading.ts
│ ├── getPosts.ts
│ └── types.d.ts
├── redis.ts
├── reteLimiter.ts
├── s3.ts
└── s3
│ ├── client.ts
│ ├── deleteFileFromS3.ts
│ ├── uploadImageToS3.ts
│ ├── uploadLargeFileToS3.ts
│ └── uploadSmallFileToS3.ts
├── middleware
├── _verifyHeaderCookie.ts
├── auth.ts
└── middleware.ts
├── migration
├── fetchVNDBInfo.mjs
├── migrateAlias.mjs
├── saveAlias.mjs
├── syncResourceCategories.mjs
├── updatePatchResourceUpdateTime.mjs
├── updateReleased.mjs
├── updateResourceUpdateTime.mjs
└── userDailyUploadSize.mjs
├── motion
├── bell.ts
├── card.ts
└── sooner.ts
├── next.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── posts
├── dev
│ └── documentation.mdx
├── galgame
│ └── resource.mdx
├── kun
│ ├── moe.mdx
│ └── ren.mdx
└── notice
│ ├── about.mdx
│ ├── cfmsc.mdx
│ ├── creator.mdx
│ ├── feedback.mdx
│ ├── galgame-tutorial.mdx
│ ├── moemoepoint.mdx
│ ├── nsfw.mdx
│ ├── open-source.mdx
│ ├── paradigm.mdx
│ ├── patch-tutorial.mdx
│ ├── privacy.mdx
│ ├── rule.mdx
│ └── update.mdx
├── prisma
├── index.ts
└── schema.prisma
├── public
├── apple-touch-icon.avif
├── edit
│ ├── 1.avif
│ └── 2.avif
├── favicon.ico
├── favicon.webp
├── kungalgame.avif
├── placeholder.webp
├── posts
│ ├── dev
│ │ └── documentation
│ │ │ └── banner.avif
│ ├── galgame
│ │ └── resource
│ │ │ └── banner.avif
│ ├── kun
│ │ ├── moe
│ │ │ ├── banner.avif
│ │ │ └── kun.webp
│ │ └── ren
│ │ │ ├── banner.avif
│ │ │ ├── ren1.avif
│ │ │ └── ren2.avif
│ └── notice
│ │ ├── about
│ │ └── banner.avif
│ │ ├── cfmsc
│ │ └── banner.avif
│ │ ├── creator
│ │ └── banner.avif
│ │ ├── feedback
│ │ └── banner.avif
│ │ ├── galgame-tutorial
│ │ └── banner.avif
│ │ ├── moemoepoint
│ │ └── banner.avif
│ │ ├── nsfw
│ │ └── banner.avif
│ │ ├── open-source
│ │ └── banner.avif
│ │ ├── paradigm
│ │ └── banner.avif
│ │ ├── patch-tutorial
│ │ ├── banner.avif
│ │ ├── image1.avif
│ │ ├── image2.avif
│ │ ├── image3.avif
│ │ ├── image4.avif
│ │ ├── image5.avif
│ │ ├── image6.avif
│ │ ├── image7.avif
│ │ └── image8.avif
│ │ ├── privacy
│ │ └── banner.avif
│ │ ├── rule
│ │ └── banner.avif
│ │ └── update
│ │ └── banner.avif
├── robots.txt
└── sooner
│ ├── あーちゃん.webp
│ ├── こじかひわ.webp
│ ├── 琥珀.webp
│ └── 雪々.webp
├── scripts
├── deployBuild.ts
├── deployInstall.ts
├── dynamic-routes
│ ├── getKunDynamicBlog.ts
│ └── getKunDynamicRoutes.ts
├── generateKunSitemap.ts
└── postbuild.ts
├── server
├── cron.ts
├── image
│ └── auth
│ │ ├── other
│ │ ├── 22.webp
│ │ ├── 24.webp
│ │ ├── 29.webp
│ │ ├── 37.webp
│ │ ├── 38.webp
│ │ ├── 46.webp
│ │ ├── 50.webp
│ │ ├── 54.webp
│ │ ├── 59.webp
│ │ ├── 64.webp
│ │ ├── 65.webp
│ │ ├── 70.webp
│ │ └── 71.webp
│ │ └── white
│ │ ├── 14.webp
│ │ ├── 16.webp
│ │ ├── 26.webp
│ │ ├── 51.webp
│ │ ├── 59.webp
│ │ ├── 6.webp
│ │ ├── 7.webp
│ │ ├── 76.webp
│ │ ├── 8.webp
│ │ └── 9.webp
└── tasks
│ ├── resetDailyTask.ts
│ └── setCleanupTask.ts
├── store
├── _cookie.ts
├── breadcrumb.ts
├── editStore.ts
├── milkdownStore.ts
├── rewriteStore.ts
├── searchStore.ts
├── settingStore.ts
└── userStore.ts
├── styles
├── blog.scss
├── editor.scss
├── index.scss
├── prose.scss
└── tailwind.scss
├── tailwind.config.js
├── tsconfig.json
├── types
├── api
│ ├── admin.d.ts
│ ├── comment.d.ts
│ ├── company.d.ts
│ ├── galgame.d.ts
│ ├── home.d.ts
│ ├── message.d.ts
│ ├── patch.d.ts
│ ├── ranking.d.ts
│ ├── release.d.ts
│ ├── resource.d.ts
│ ├── tag.d.ts
│ ├── upload.d.ts
│ └── user.d.ts
├── response.d.ts
└── user.d.ts
├── utils
├── actions
│ ├── getNSFWHeader.ts
│ ├── safeParseSchema.ts
│ └── verifyHeaderCookie.ts
├── cn.ts
├── cookies.ts
├── dataURItoBlob.ts
├── formatDistanceToNow.ts
├── formatNumber.ts
├── kunCopy.ts
├── kunErrorHandler.ts
├── kunFetch.ts
├── lz.ts
├── markdownToText.tsx
├── noIndex.tsx
├── random.ts
├── resizeImage.ts
├── sanitizeFileName.ts
├── throttle.ts
├── time.ts
└── validate.ts
└── validations
├── admin.ts
├── auth.ts
├── comment.ts
├── company.ts
├── dotenv-check.ts
├── edit.ts
├── forgot.ts
├── galgame.ts
├── message.ts
├── patch.ts
├── release.ts
├── resource.ts
├── search.ts
├── tag.ts
├── user.ts
└── walkthrough.ts
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/devcontainers/javascript-node:20
2 |
3 | # Install pnpm
4 | RUN su node -c "npm i -g pnpm"
5 |
6 | # Install additional OS packages
7 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
8 | && apt-get -y install --no-install-recommends postgresql-client redis-tools
9 |
10 | # Clean up
11 | RUN apt-get autoremove -y \
12 | && apt-get clean -y \
13 | && rm -rf /var/lib/apt/lists/*
14 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kungalgame Patch Development Environment",
3 | "dockerComposeFile": "docker-compose.yml",
4 | "service": "app",
5 | "workspaceFolder": "/workspace",
6 | "customizations": {
7 | "vscode": {
8 | "settings": {
9 | "eslint.alwaysShowStatus": true,
10 | "eslint.codeActionsOnSave.mode": "all",
11 | "eslint.enable": true,
12 | "eslint.format.enable": true,
13 | "editor.formatOnSave": true,
14 | "prettier.requireConfig": true
15 | }
16 | }
17 | },
18 | "features": {
19 | "ghcr.io/devcontainers/features/node:1": {
20 | "version": "22"
21 | },
22 | "ghcr.io/devcontainers/features/git:1": {},
23 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}
24 | },
25 | "forwardPorts": [3000, 5432, 6379],
26 | "postCreateCommand": "pnpm install && pnpm prisma:push"
27 | }
28 |
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | app:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | volumes:
9 | - ..:/workspace:cached
10 | command: sleep infinity
11 | network_mode: service:db
12 | environment:
13 | - NODE_ENV=development
14 | - KUN_DATABASE_URL=postgresql://postgres:kunloveren@localhost:5432/kungalgame_patch?schema=public
15 | - REDIS_HOST=localhost
16 | - REDIS_PORT=6379
17 |
18 | db:
19 | image: postgres:latest
20 | restart: unless-stopped
21 | volumes:
22 | - postgres-data:/var/lib/postgresql/data
23 | environment:
24 | POSTGRES_PASSWORD: kunloveren
25 | POSTGRES_USER: postgres
26 | POSTGRES_DB: kungalgame_patch
27 |
28 | redis:
29 | image: redis:latest
30 | restart: unless-stopped
31 | volumes:
32 | - redis-data:/data
33 | network_mode: service:db
34 |
35 | volumes:
36 | postgres-data:
37 | redis-data:
38 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageRules": [
3 | {
4 | "managers": ["npm"],
5 | "packageNames": ["pnpm"],
6 | "enabled": true,
7 | "rangeStrategy": "bump"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.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 | .yarn/install-state.gz
8 |
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 |
38 | # env
39 | .env
40 |
41 | # uploads
42 | /uploads
43 |
44 | # backup
45 | /backup
46 |
47 | # sitemap
48 | /public/**/*.xml
49 |
50 | # IDX
51 | .idx/.data
52 |
53 | # migration cache
54 | migration/*.json
55 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | public-hoist-pattern[]=*@nextui-org/*
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .next
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "singleQuote": true,
4 | "semi": false,
5 | "bracketSpacing": true,
6 | "tabWidth": 2,
7 | "useTabs": false,
8 | "files.insertFinalNewline": true,
9 | "htmlWhitespaceSensitivity": "strict",
10 | "trailingComma": "none",
11 | "endOfLine": "auto"
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "bradlc.vscode-tailwindcss",
4 | "dbaeumer.vscode-eslint",
5 | "EditorConfig.EditorConfig",
6 | "esbenp.prettier-vscode",
7 | "mgmcdermott.vscode-language-babel",
8 | "streetsidesoftware.code-spell-checker",
9 | "stylelint.vscode-stylelint",
10 | "syler.sass-indented",
11 | "webben.browserslist"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/app/about/layout.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react'
2 | import { KunSidebar } from '~/components/about/Sidebar'
3 | import { getDirectoryTree } from '~/lib/mdx/directoryTree'
4 |
5 | interface LayoutProps {
6 | children: ReactNode
7 | }
8 |
9 | export default function Layout({ children }: LayoutProps) {
10 | const tree = getDirectoryTree()
11 |
12 | return (
13 |
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/app/about/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '关于我们 | 网站博客',
6 | description: `${kunMoyuMoe.titleShort} 是一个非盈利的, 由社区驱动的, 完全开源免费的 Galgame 补丁资源下载网站。它由现代框架 Next.js 驱动, 为保证最好的性能, 恳请各位朋友提出宝贵的意见`,
7 | openGraph: {
8 | title: '关于我们 | 网站博客',
9 | description: `${kunMoyuMoe.titleShort} 是一个非盈利的, 由社区驱动的, 完全开源免费的 Galgame 补丁资源下载网站。它由现代框架 Next.js 驱动, 为保证最好的性能, 恳请各位朋友提出宝贵的意见`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '关于我们 | 网站博客',
16 | description: `${kunMoyuMoe.titleShort} 是一个非盈利的, 由社区驱动的, 完全开源免费的 Galgame 补丁资源下载网站。它由现代框架 Next.js 驱动, 为保证最好的性能, 恳请各位朋友提出宝贵的意见`
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { getNSFWHeader } from '~/utils/actions/getNSFWHeader'
4 | import { getHomeData } from '~/app/api/home/route'
5 |
6 | export const kunGetActions = async () => {
7 | const nsfwEnable = await getNSFWHeader()
8 | const response = await getHomeData(nsfwEnable)
9 | return response
10 | }
11 |
--------------------------------------------------------------------------------
/app/admin/comment/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getComment } from '~/app/api/admin/comment/get'
6 | import { adminPaginationSchema } from '~/validations/admin'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getComment(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/comment/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `评论管理 - ${kunMoyuMoe.titleShort}`,
6 | description: `管理 ${kunMoyuMoe.titleShort} 所有的评论, 查询所有评论, 删除评论, 更改评论内容, 查看评论列表 等等`,
7 | openGraph: {
8 | title: `评论管理 - ${kunMoyuMoe.titleShort}`,
9 | description: `管理 ${kunMoyuMoe.titleShort} 所有的评论, 查询所有评论, 删除评论, 更改评论内容, 查看评论列表 等等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `评论管理 - ${kunMoyuMoe.titleShort}`,
16 | description: `管理 ${kunMoyuMoe.titleShort} 所有的评论, 查询所有评论, 删除评论, 更改评论内容, 查看评论列表 等等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/comment/page.tsx:
--------------------------------------------------------------------------------
1 | import { Comment } from '~/components/admin/comment/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return (
21 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/admin/creator/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { adminPaginationSchema } from '~/validations/admin'
6 | import { getAdminCreator } from '~/app/api/admin/creator/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getAdminCreator(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/creator/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `创作者管理 - ${kunMoyuMoe.titleShort}`,
6 | description: `管理 ${kunMoyuMoe.titleShort} 所有的创作者, 同意创作者请求, 拒绝创作者请求, 查看创作者列表 等等`,
7 | openGraph: {
8 | title: `创作者管理 - ${kunMoyuMoe.titleShort}`,
9 | description: `管理 ${kunMoyuMoe.titleShort} 所有的创作者, 同意创作者请求, 拒绝创作者请求, 查看创作者列表 等等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `创作者管理 - ${kunMoyuMoe.titleShort}`,
16 | description: `管理 ${kunMoyuMoe.titleShort} 所有的创作者, 同意创作者请求, 拒绝创作者请求, 查看创作者列表 等等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/creator/page.tsx:
--------------------------------------------------------------------------------
1 | import { Creator } from '~/components/admin/creator/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/galgame/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { adminPaginationSchema } from '~/validations/admin'
6 | import { getGalgame } from '~/app/api/admin/galgame/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getGalgame(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/galgame/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `Galgame 管理 - ${kunMoyuMoe.titleShort}`,
6 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame, 包括创建新 Galgame, 删除 Galgame, 封禁 Galgame, 查询 Galgame, 更改 Galgame 介绍, 标签, 会社 等等`,
7 | openGraph: {
8 | title: `Galgame 管理 - ${kunMoyuMoe.titleShort}`,
9 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame, 包括创建新 Galgame, 删除 Galgame, 封禁 Galgame, 查询 Galgame, 更改 Galgame 介绍, 标签, 会社 等等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `Galgame 管理 - ${kunMoyuMoe.titleShort}`,
16 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame, 包括创建新 Galgame, 删除 Galgame, 封禁 Galgame, 查询 Galgame, 更改 Galgame 介绍, 标签, 会社 等等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/galgame/page.tsx:
--------------------------------------------------------------------------------
1 | import { Galgame } from '~/components/admin/galgame/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return (
21 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/admin/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Sidebar } from '~/components/admin/Sidebar'
2 | // import { Navbar } from '~/components/admin/Navbar'
3 | import { kunMetadata } from './metadata'
4 | import type { Metadata } from 'next'
5 |
6 | export const metadata: Metadata = kunMetadata
7 |
8 | interface Props {
9 | children: React.ReactNode
10 | }
11 |
12 | export default function AdminLayout({ children }: Props) {
13 | return (
14 |
15 |
16 |
17 | {/*
*/}
18 |
{children}
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/admin/log/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { adminPaginationSchema } from '~/validations/admin'
6 | import { getLog } from '~/app/api/admin/log/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getLog(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/log/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `管理日志 - ${kunMoyuMoe.titleShort}`,
6 | description: `${kunMoyuMoe.titleShort} 管理系统所有的操作记录, 更改用户权限, 封禁用户, 删除 Galgame 补丁资源, 删除评论, 更改 Galgame 介绍, 标签, 别名等`,
7 | openGraph: {
8 | title: `管理日志 - ${kunMoyuMoe.titleShort}`,
9 | description: `${kunMoyuMoe.titleShort} 管理系统所有的操作记录, 更改用户权限, 封禁用户, 删除 Galgame 补丁资源, 删除评论, 更改 Galgame 介绍, 标签, 别名等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `管理日志 - ${kunMoyuMoe.titleShort}`,
16 | description: `${kunMoyuMoe.titleShort} 管理系统所有的操作记录, 更改用户权限, 封禁用户, 删除 Galgame 补丁资源, 删除评论, 更改 Galgame 介绍, 标签, 别名等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/log/page.tsx:
--------------------------------------------------------------------------------
1 | import { Log } from '~/components/admin/log/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `管理系统`,
6 | description: `为 ${kunMoyuMoe.titleShort} 定制的管理系统, 可以查看 网站概览 用户总数量, 补丁总数量, 今日评论数, 日活跃用户, 包括 Galgame 管理, 补丁资源管理, 用户管理, 创作者管理, 评论管理, 系统设置, 管理日志 等等`,
7 | openGraph: {
8 | title: '管理系统',
9 | description: `为 ${kunMoyuMoe.titleShort} 定制的管理系统, 可以查看 网站概览 用户总数量, 补丁总数量, 今日评论数, 日活跃用户, 包括 Galgame 管理, 补丁资源管理, 用户管理, 创作者管理, 评论管理, 系统设置, 管理日志 等等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '管理系统',
16 | description: `为 ${kunMoyuMoe.titleShort} 定制的管理系统, 可以查看 网站概览 用户总数量, 补丁总数量, 今日评论数, 日活跃用户, 包括 Galgame 管理, 补丁资源管理, 用户管理, 创作者管理, 评论管理, 系统设置, 管理日志 等等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/page.tsx:
--------------------------------------------------------------------------------
1 | import { KunStats } from '~/components/admin/stats/KunStats'
2 |
3 | export default async function Kun() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/app/admin/resource/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { adminPaginationSchema } from '~/validations/admin'
6 | import { getPatchResource } from '~/app/api/admin/resource/get'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getPatchResource(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/resource/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `补丁管理 - ${kunMoyuMoe.titleShort}`,
6 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame 补丁下载资源, 创建资源, 更改资源类型, 语言, 平台, 删除 Galgame 补丁资源 等`,
7 | openGraph: {
8 | title: `补丁管理 - ${kunMoyuMoe.titleShort}`,
9 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame 补丁下载资源, 创建资源, 更改资源类型, 语言, 平台, 删除 Galgame 补丁资源 等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `补丁管理 - ${kunMoyuMoe.titleShort}`,
16 | description: `管理 ${kunMoyuMoe.titleShort} 所有的 Galgame 补丁下载资源, 创建资源, 更改资源类型, 语言, 平台, 删除 Galgame 补丁资源 等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/resource/page.tsx:
--------------------------------------------------------------------------------
1 | import { Resource } from '~/components/admin/resource/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return (
21 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/admin/setting/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { getDisableRegisterStatus } from '~/app/api/admin/setting/register/route'
4 | import { getCommentVerifyStatus } from '~/app/api/admin/setting/comment/getCommentVerifyStatus'
5 | import { getEnableOnlyCreatorCreateStatus } from '~/app/api/admin/setting/creator/getEnableOnlyCreatorCreateStatus'
6 |
7 | export const kunGetDisableRegisterStatusActions = async () => {
8 | const response = await getDisableRegisterStatus()
9 | return response
10 | }
11 |
12 | export const kunGetCommentVerifyStatusActions = async () => {
13 | const response = await getCommentVerifyStatus()
14 | return response
15 | }
16 |
17 | export const kunGetEnableOnlyCreatorCreateStatus = async () => {
18 | const response = await getEnableOnlyCreatorCreateStatus()
19 | return response
20 | }
21 |
--------------------------------------------------------------------------------
/app/admin/setting/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `系统设置 - ${kunMoyuMoe.titleShort}`,
6 | description: `${kunMoyuMoe.titleShort} 的数据库备份, 鉴权设置, 认证设置, 网站整体风格设置, 启用维护模式 等`,
7 | openGraph: {
8 | title: `系统设置 - ${kunMoyuMoe.titleShort}`,
9 | description: `${kunMoyuMoe.titleShort} 的数据库备份, 鉴权设置, 认证设置, 网站整体风格设置, 启用维护模式 等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `系统设置 - ${kunMoyuMoe.titleShort}`,
16 | description: `${kunMoyuMoe.titleShort} 的数据库备份, 鉴权设置, 认证设置, 网站整体风格设置, 启用维护模式 等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin/setting`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/setting/page.tsx:
--------------------------------------------------------------------------------
1 | import { AdminSetting } from '~/components/admin/setting/Container'
2 | import { kunMetadata } from './metadata'
3 | import {
4 | kunGetDisableRegisterStatusActions,
5 | kunGetCommentVerifyStatusActions,
6 | kunGetEnableOnlyCreatorCreateStatus
7 | } from './actions'
8 | import type { Metadata } from 'next'
9 |
10 | export const revalidate = 5
11 |
12 | export const metadata: Metadata = kunMetadata
13 |
14 | export default async function Kun() {
15 | const { disableRegister } = await kunGetDisableRegisterStatusActions()
16 | const { enableCommentVerify } = await kunGetCommentVerifyStatusActions()
17 | const { enableOnlyCreatorCreate } =
18 | await kunGetEnableOnlyCreatorCreateStatus()
19 |
20 | return (
21 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/admin/user/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { adminPaginationSchema } from '~/validations/admin'
6 | import { getUserInfo } from '~/app/api/admin/user/get'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(adminPaginationSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserInfo(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/admin/user/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: `用户管理 - ${kunMoyuMoe.titleShort}`,
6 | description: `管理 ${kunMoyuMoe.titleShort} 的所有用户, 更改用户权限, 设置用户为管理员, 设置用户为创作者, 封禁用户 等`,
7 | openGraph: {
8 | title: `用户管理 - ${kunMoyuMoe.titleShort}`,
9 | description: `管理 ${kunMoyuMoe.titleShort} 的所有用户, 更改用户权限, 设置用户为管理员, 设置用户为创作者, 封禁用户 等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: `用户管理 - ${kunMoyuMoe.titleShort}`,
16 | description: `管理 ${kunMoyuMoe.titleShort} 的所有用户, 更改用户权限, 设置用户为管理员, 设置用户为创作者, 封禁用户 等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/admin/user`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/admin/user/page.tsx:
--------------------------------------------------------------------------------
1 | import { User } from '~/components/admin/user/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 30
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return
21 | }
22 |
--------------------------------------------------------------------------------
/app/api/admin/setting/comment/getCommentVerifyStatus.ts:
--------------------------------------------------------------------------------
1 | import { getKv } from '~/lib/redis'
2 | import { KUN_PATCH_ENABLE_COMMENT_VERIFY_KEY } from '~/config/redis'
3 |
4 | export const getCommentVerifyStatus = async () => {
5 | const isEnableCommentVerify = await getKv(KUN_PATCH_ENABLE_COMMENT_VERIFY_KEY)
6 | return {
7 | enableCommentVerify: !!isEnableCommentVerify
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/api/admin/setting/creator/getEnableOnlyCreatorCreateStatus.ts:
--------------------------------------------------------------------------------
1 | import { getKv } from '~/lib/redis'
2 | import { KUN_PATCH_ENABLE_ONLY_CREATOR_CREATE_KEY } from '~/config/redis'
3 |
4 | export const getEnableOnlyCreatorCreateStatus = async () => {
5 | const isEnableOnlyCreatorCreate = await getKv(
6 | KUN_PATCH_ENABLE_ONLY_CREATOR_CREATE_KEY
7 | )
8 | return {
9 | enableOnlyCreatorCreate: !!isEnableOnlyCreatorCreate
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/api/admin/stats/sum/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import { prisma } from '~/prisma/index'
3 | import type { SumData } from '~/types/api/admin'
4 |
5 | export const getSumData = async (): Promise => {
6 | const [userCount, galgameCount, patchResourceCount, patchCommentCount] =
7 | await Promise.all([
8 | prisma.user.count(),
9 | prisma.patch.count(),
10 | prisma.patch_resource.count(),
11 | prisma.patch_comment.count()
12 | ])
13 |
14 | return {
15 | userCount,
16 | galgameCount,
17 | patchResourceCount,
18 | patchCommentCount
19 | }
20 | }
21 |
22 | export const GET = async () => {
23 | const data = await getSumData()
24 | return NextResponse.json(data)
25 | }
26 |
--------------------------------------------------------------------------------
/app/api/apply/status/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
3 | import { prisma } from '~/prisma/index'
4 |
5 | export const getApplyStatus = async (uid: number) => {
6 | const count = await prisma.patch_resource.count({
7 | where: { user_id: uid }
8 | })
9 | const user = await prisma.user.findUnique({
10 | where: { id: uid }
11 | })
12 | const role = user?.role ?? 0
13 |
14 | return { count, role }
15 | }
16 |
17 | export const GET = async (req: NextRequest) => {
18 | const payload = await verifyHeaderCookie(req)
19 |
20 | const response = await getApplyStatus(payload?.uid ?? 0)
21 | return NextResponse.json(response)
22 | }
23 |
--------------------------------------------------------------------------------
/app/api/auth/captcha/_utils.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises'
2 | import path from 'path'
3 |
4 | export const shuffleKunArray = (array: T[]): T[] => {
5 | const shuffled = [...array]
6 | for (let i = shuffled.length - 1; i > 0; i--) {
7 | const j = Math.floor(Math.random() * (i + 1))
8 | ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
9 | }
10 | return shuffled
11 | }
12 |
13 | export const readImageBase64 = async (filePath: string) => {
14 | const fileBuffer = await fs.readFile(filePath)
15 | return `data:image/${path.extname(filePath).substring(1)};base64,${fileBuffer.toString('base64')}`
16 | }
17 |
--------------------------------------------------------------------------------
/app/api/auth/captcha/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { kunParsePostBody } from '~/app/api/utils/parseQuery'
3 | import { generateCaptcha } from './generate'
4 | import { verifyKunCaptcha } from '~/app/api/utils/verifyKunCaptcha'
5 | import { captchaSchema } from '~/validations/auth'
6 |
7 | export const GET = async () => {
8 | const captcha = await generateCaptcha()
9 | return NextResponse.json(captcha)
10 | }
11 |
12 | export const POST = async (req: NextRequest) => {
13 | const input = await kunParsePostBody(req, captchaSchema)
14 | if (typeof input === 'string') {
15 | return NextResponse.json(input)
16 | }
17 |
18 | const captcha = await verifyKunCaptcha(input.sessionId, input.selectedIds)
19 | return NextResponse.json(captcha)
20 | }
21 |
--------------------------------------------------------------------------------
/app/api/edit/duplicate/route.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { NextRequest, NextResponse } from 'next/server'
3 | import { kunParseGetQuery } from '~/app/api/utils/parseQuery'
4 | import { prisma } from '~/prisma/index'
5 | import { duplicateSchema } from '~/validations/edit'
6 |
7 | export const duplicate = async (input: z.infer) => {
8 | const patch = await prisma.patch.findFirst({
9 | where: { vndb_id: input.vndbId }
10 | })
11 | if (patch) {
12 | return 'VNDB ID 重复, 本游戏已经被发布过了'
13 | }
14 | return {}
15 | }
16 |
17 | export const GET = async (req: NextRequest) => {
18 | const input = kunParseGetQuery(req, duplicateSchema)
19 | if (typeof input === 'string') {
20 | return NextResponse.json(input)
21 | }
22 |
23 | const response = await duplicate(input)
24 | return NextResponse.json(response)
25 | }
26 |
--------------------------------------------------------------------------------
/app/api/message/read/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { prisma } from '~/prisma/index'
3 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
4 |
5 | export const readMessage = async (uid: number) => {
6 | await prisma.user_message.updateMany({
7 | where: { recipient_id: uid },
8 | data: { status: { set: 1 } }
9 | })
10 | return {}
11 | }
12 |
13 | export const PUT = async (req: NextRequest) => {
14 | const payload = await verifyHeaderCookie(req)
15 | if (!payload) {
16 | return NextResponse.json('用户未登录')
17 | }
18 |
19 | const response = await readMessage(payload.uid)
20 | return NextResponse.json(response)
21 | }
22 |
--------------------------------------------------------------------------------
/app/api/message/unread/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { prisma } from '~/prisma/index'
3 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
4 |
5 | export const getMessage = async (uid: number) => {
6 | const unread = await prisma.user_message.findMany({
7 | where: { recipient_id: uid, status: 0 },
8 | select: { type: true }
9 | })
10 | return [...new Set(unread.map((message) => message.type))]
11 | }
12 |
13 | export const GET = async (req: NextRequest) => {
14 | const payload = await verifyHeaderCookie(req)
15 | if (!payload) {
16 | return NextResponse.json('用户未登录')
17 | }
18 |
19 | const response = await getMessage(payload.uid)
20 | return NextResponse.json(response)
21 | }
22 |
--------------------------------------------------------------------------------
/app/api/patch/comment/_helpers.ts:
--------------------------------------------------------------------------------
1 | import { convert } from 'html-to-text'
2 | import type { PatchComment } from '~/types/api/patch'
3 |
4 | export const nestComments = (flatComments: PatchComment[]): PatchComment[] => {
5 | const commentMap: { [key: number]: PatchComment } = {}
6 |
7 | flatComments.forEach((comment) => {
8 | comment.reply = []
9 | commentMap[comment.id] = { ...comment, quotedContent: null }
10 | })
11 |
12 | const nestedComments: PatchComment[] = []
13 |
14 | flatComments.forEach((comment) => {
15 | if (comment.parentId) {
16 | const parentComment = commentMap[comment.parentId]
17 | if (parentComment) {
18 | parentComment.reply.push(comment)
19 | comment.quotedContent = convert(
20 | commentMap[comment.parentId].content
21 | ).slice(0, 107)
22 | comment.quotedUsername = commentMap[comment.parentId].user.name
23 | }
24 | } else {
25 | nestedComments.push(comment)
26 | }
27 | })
28 |
29 | return nestedComments
30 | }
31 |
--------------------------------------------------------------------------------
/app/api/patch/comment/markdown/route.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { NextRequest, NextResponse } from 'next/server'
3 | import { kunParseGetQuery } from '~/app/api/utils/parseQuery'
4 | import { prisma } from '~/prisma/index'
5 |
6 | const commentIdSchema = z.object({
7 | commentId: z.coerce
8 | .number({ message: '评论 ID 必须为数字' })
9 | .min(1)
10 | .max(9999999)
11 | })
12 |
13 | export const getCommentMarkdown = async (
14 | input: z.infer
15 | ) => {
16 | const { commentId } = input
17 |
18 | const comment = await prisma.patch_comment.findUnique({
19 | where: { id: commentId },
20 | select: {
21 | content: true
22 | }
23 | })
24 |
25 | return { content: comment?.content ?? '' }
26 | }
27 |
28 | export const GET = async (req: NextRequest) => {
29 | const input = kunParseGetQuery(req, commentIdSchema)
30 | if (typeof input === 'string') {
31 | return NextResponse.json(input)
32 | }
33 |
34 | const response = await getCommentMarkdown(input)
35 | return NextResponse.json(response)
36 | }
37 |
--------------------------------------------------------------------------------
/app/api/patch/comment/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { prisma } from '~/prisma/index'
3 | import { patchCommentUpdateSchema } from '~/validations/patch'
4 |
5 | export const updateComment = async (
6 | input: z.infer,
7 | uid: number
8 | ) => {
9 | const { commentId, content } = input
10 |
11 | await prisma.patch_comment.update({
12 | where: { id: commentId, user_id: uid },
13 | data: {
14 | content,
15 | edit: Date.now().toString()
16 | },
17 | include: {
18 | user: true,
19 | like_by: {
20 | include: {
21 | user: true
22 | }
23 | }
24 | }
25 | })
26 | return {}
27 | }
28 |
--------------------------------------------------------------------------------
/app/api/patch/walkthrough/delete.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { prisma } from '~/prisma/index'
3 |
4 | const walkthroughIdSchema = z.object({
5 | walkthroughId: z.coerce.number().min(1).max(9999999)
6 | })
7 |
8 | export const deleteWalkthrough = async (
9 | input: z.infer,
10 | uid: number
11 | ) => {
12 | const patchWalkthrough = await prisma.patch_walkthrough.findUnique({
13 | where: {
14 | id: input.walkthroughId,
15 | user_id: uid
16 | }
17 | })
18 | if (!patchWalkthrough) {
19 | return '未找到对应的攻略'
20 | }
21 | if (patchWalkthrough.user_id !== uid) {
22 | return '您没有权限删除该攻略'
23 | }
24 |
25 | await prisma.patch_walkthrough.delete({
26 | where: { id: input.walkthroughId }
27 | })
28 | return {}
29 | }
30 |
--------------------------------------------------------------------------------
/app/api/patch/walkthrough/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { prisma } from '~/prisma/index'
3 | import { updateWalkthroughSchema } from '~/validations/walkthrough'
4 |
5 | export const updateWalkthrough = async (
6 | input: z.infer,
7 | uid: number
8 | ) => {
9 | const { walkthroughId, name, content } = input
10 | const walkthrough = await prisma.patch_walkthrough.findUnique({
11 | where: { id: walkthroughId }
12 | })
13 | if (!walkthrough) {
14 | return '未找到该攻略'
15 | }
16 | if (walkthrough.user_id !== uid) {
17 | return '您没有权限更改该攻略'
18 | }
19 |
20 | await prisma.patch_walkthrough.update({
21 | where: { id: walkthroughId, user_id: uid },
22 | data: { name, content }
23 | })
24 |
25 | return {}
26 | }
27 |
--------------------------------------------------------------------------------
/app/api/user/image/_upload.ts:
--------------------------------------------------------------------------------
1 | import sharp from 'sharp'
2 | import { uploadImageToS3 } from '~/lib/s3/uploadImageToS3'
3 | import { checkBufferSize } from '~/app/api/utils/checkBufferSize'
4 |
5 | export const uploadIntroductionImage = async (
6 | name: string,
7 | image: ArrayBuffer,
8 | uid: number
9 | ) => {
10 | const minImage = await sharp(image)
11 | .resize(1920, 1080, {
12 | fit: 'inside',
13 | withoutEnlargement: true
14 | })
15 | .avif({ quality: 30 })
16 | .toBuffer()
17 |
18 | if (!checkBufferSize(minImage, 1.007)) {
19 | return '图片体积过大'
20 | }
21 |
22 | const bucketName = `user_${uid}/image`
23 | await uploadImageToS3(`${bucketName}/${name}.avif`, minImage)
24 | }
25 |
--------------------------------------------------------------------------------
/app/api/user/mention/search/route.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { NextRequest, NextResponse } from 'next/server'
3 | import { kunParseGetQuery } from '~/app/api/utils/parseQuery'
4 | import { prisma } from '~/prisma/index'
5 | import { searchUserSchema } from '~/validations/user'
6 |
7 | export const searchUser = async (input: z.infer) => {
8 | const { query } = input
9 |
10 | const users: KunUser[] = await prisma.user.findMany({
11 | where: {
12 | name: { contains: query, mode: 'insensitive' }
13 | },
14 | select: {
15 | id: true,
16 | name: true,
17 | avatar: true
18 | },
19 | take: 50
20 | })
21 |
22 | return users
23 | }
24 |
25 | export const GET = async (req: NextRequest) => {
26 | const input = kunParseGetQuery(req, searchUserSchema)
27 | if (typeof input === 'string') {
28 | return NextResponse.json(input)
29 | }
30 |
31 | const response = await searchUser(input)
32 | return NextResponse.json(response)
33 | }
34 |
--------------------------------------------------------------------------------
/app/api/user/setting/_upload.ts:
--------------------------------------------------------------------------------
1 | import sharp from 'sharp'
2 |
3 | import { MAX_SIZE, COMPRESS_QUALITY } from '~/app/api/utils/constants'
4 | import { uploadImageToS3 } from '~/lib/s3/uploadImageToS3'
5 | import { checkBufferSize } from '~/app/api/utils/checkBufferSize'
6 |
7 | export const uploadUserAvatar = async (image: ArrayBuffer, uid: number) => {
8 | const avatar = await sharp(image)
9 | .resize(256, 256, {
10 | fit: 'inside',
11 | withoutEnlargement: true
12 | })
13 | .avif({ quality: COMPRESS_QUALITY })
14 | .toBuffer()
15 | const miniAvatar = await sharp(image)
16 | .resize(100, 100, {
17 | fit: 'inside',
18 | withoutEnlargement: true
19 | })
20 | .avif({ quality: COMPRESS_QUALITY })
21 | .toBuffer()
22 |
23 | if (!checkBufferSize(avatar, MAX_SIZE)) {
24 | return '图片体积过大'
25 | }
26 |
27 | const bucketName = `user/avatar/user_${uid}`
28 |
29 | await uploadImageToS3(`${bucketName}/avatar.avif`, avatar)
30 | await uploadImageToS3(`${bucketName}/avatar-mini.avif`, miniAvatar)
31 | }
32 |
--------------------------------------------------------------------------------
/app/api/user/setting/bio/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { kunParsePostBody } from '~/app/api/utils/parseQuery'
3 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
4 | import { prisma } from '~/prisma/index'
5 | import { bioSchema } from '~/validations/user'
6 |
7 | export const POST = async (req: NextRequest) => {
8 | const input = await kunParsePostBody(req, bioSchema)
9 | if (typeof input === 'string') {
10 | return NextResponse.json(input)
11 | }
12 | const payload = await verifyHeaderCookie(req)
13 | if (!payload) {
14 | return NextResponse.json('用户未登录')
15 | }
16 |
17 | await prisma.user.update({
18 | where: { id: payload.uid },
19 | data: { bio: input.bio }
20 | })
21 |
22 | return NextResponse.json({})
23 | }
24 |
--------------------------------------------------------------------------------
/app/api/user/status/check-in/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { prisma } from '~/prisma/index'
3 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
4 | import { randomNum } from '~/utils/random'
5 |
6 | const checkIn = async (uid: number) => {
7 | const user = await prisma.user.findUnique({
8 | where: { id: uid }
9 | })
10 | if (!user) {
11 | return '用户未找到'
12 | }
13 | if (user.daily_check_in) {
14 | return '您今天已经签到过了'
15 | }
16 |
17 | const randomMoemoepoints = randomNum(0, 7)
18 |
19 | await prisma.user.update({
20 | where: { id: uid },
21 | data: {
22 | moemoepoint: { increment: randomMoemoepoints },
23 | daily_check_in: { set: 1 }
24 | }
25 | })
26 |
27 | return { randomMoemoepoints }
28 | }
29 |
30 | export async function POST(req: NextRequest) {
31 | const payload = await verifyHeaderCookie(req)
32 | if (!payload) {
33 | return NextResponse.json('用户未登录')
34 | }
35 |
36 | const res = await checkIn(payload.uid)
37 | return NextResponse.json(res)
38 | }
39 |
--------------------------------------------------------------------------------
/app/api/user/status/logout/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { cookies } from 'next/headers'
3 | import { verifyHeaderCookie } from '~/middleware/_verifyHeaderCookie'
4 | import { deleteKunToken } from '~/app/api/utils/jwt'
5 |
6 | export async function POST(req: NextRequest) {
7 | const payload = await verifyHeaderCookie(req)
8 | if (!payload) {
9 | return NextResponse.json('用户未登录')
10 | }
11 |
12 | await deleteKunToken(payload.uid)
13 | const cookie = await cookies()
14 | cookie.delete('kun-galgame-patch-moe-token')
15 |
16 | return NextResponse.json({ message: '退出登录成功' })
17 | }
18 |
--------------------------------------------------------------------------------
/app/api/utils/checkBufferSize.ts:
--------------------------------------------------------------------------------
1 | export const checkBufferSize = (buffer: Buffer, maxSizeInMegabyte: number) => {
2 | const maxSizeInBytes = maxSizeInMegabyte * 1024 * 1024
3 | return buffer.length <= maxSizeInBytes
4 | }
5 |
--------------------------------------------------------------------------------
/app/api/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const COMPRESS_QUALITY = 60
2 | export const MAX_SIZE = 1.007
3 |
--------------------------------------------------------------------------------
/app/api/utils/generateRandomCode.ts:
--------------------------------------------------------------------------------
1 | export const generateRandomCode = (length: number) => {
2 | const charset = '023456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ'
3 | let code = ''
4 | for (let i = 0; i < length; i++) {
5 | const randomIndex = Math.floor(Math.random() * charset.length)
6 | code += charset[randomIndex]
7 | }
8 | return code
9 | }
10 |
--------------------------------------------------------------------------------
/app/api/utils/getNSFWHeader.ts:
--------------------------------------------------------------------------------
1 | import { parseCookies } from '~/utils/cookies'
2 | import type { NextRequest } from 'next/server'
3 |
4 | export const getNSFWHeader = (req: NextRequest) => {
5 | const cookies = parseCookies(req.headers.get('cookie') ?? '')
6 | const token = cookies['kun-patch-setting-store|state|data|kunNsfwEnable']
7 | if (!token) {
8 | return { content_limit: 'sfw' }
9 | }
10 |
11 | if (token === 'all') {
12 | return {}
13 | } else {
14 | return { content_limit: token }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/api/utils/getRemoteIp.ts:
--------------------------------------------------------------------------------
1 | export const getRemoteIp = (headers: Headers): string => {
2 | const ipForwarded = () => {
3 | const ip = headers.get('x-forwarded-for')
4 | if (Array.isArray(ip)) {
5 | return ip[0]
6 | } else {
7 | return ip?.split(',')[0].trim()
8 | }
9 | }
10 |
11 | const xRealIp = headers.get('x-real-ip')
12 | const cfConnectingIp = headers.get('CF-Connecting-IP')
13 |
14 | return cfConnectingIp || ipForwarded() || xRealIp || ''
15 | }
16 |
--------------------------------------------------------------------------------
/app/api/utils/markdownToHtml.ts:
--------------------------------------------------------------------------------
1 | import rehypeSanitize from 'rehype-sanitize'
2 | import rehypeStringify from 'rehype-stringify'
3 | import remarkFrontmatter from 'remark-frontmatter'
4 | import remarkGfm from 'remark-gfm'
5 | import remarkParse from 'remark-parse'
6 | import remarkRehype from 'remark-rehype'
7 | import rehypePrism from 'rehype-prism-plus'
8 | import { unified } from 'unified'
9 |
10 | export const markdownToHtml = async (markdown: string) => {
11 | const htmlVFile = await unified()
12 | .use(remarkParse)
13 | .use(remarkRehype)
14 | .use(rehypeSanitize)
15 | .use(remarkFrontmatter)
16 | .use(remarkGfm)
17 | .use(rehypePrism, { ignoreMissing: true })
18 | .use(rehypeStringify)
19 | .process(markdown)
20 |
21 | return String(htmlVFile)
22 | }
23 |
--------------------------------------------------------------------------------
/app/api/utils/message.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from '~/prisma/index'
2 | import type { CreateMessageType } from '~/types/api/message'
3 |
4 | export const createMessage = async (data: CreateMessageType) => {
5 | const message = await prisma.user_message.create({
6 | data
7 | })
8 | return message
9 | }
10 |
11 | export const createDedupMessage = async (data: CreateMessageType) => {
12 | const duplicatedMessage = await prisma.user_message.findFirst({
13 | where: {
14 | ...data
15 | }
16 | })
17 | if (duplicatedMessage) {
18 | return
19 | }
20 |
21 | const message = createMessage(data)
22 |
23 | return message
24 | }
25 |
--------------------------------------------------------------------------------
/app/api/utils/uploadPatchBanner.ts:
--------------------------------------------------------------------------------
1 | import sharp from 'sharp'
2 |
3 | import { checkBufferSize } from '~/app/api/utils/checkBufferSize'
4 | import { MAX_SIZE, COMPRESS_QUALITY } from '~/app/api/utils/constants'
5 | import { uploadImageToS3 } from '~/lib/s3/uploadImageToS3'
6 |
7 | export const uploadPatchBanner = async (image: ArrayBuffer, id: number) => {
8 | const banner = await sharp(image)
9 | .resize(1920, 1080, {
10 | fit: 'inside',
11 | withoutEnlargement: true
12 | })
13 | .avif({ quality: COMPRESS_QUALITY })
14 | .toBuffer()
15 | const miniBanner = await sharp(image)
16 | .resize(460, 259, {
17 | fit: 'inside',
18 | withoutEnlargement: true
19 | })
20 | .avif({ quality: COMPRESS_QUALITY })
21 | .toBuffer()
22 |
23 | if (!checkBufferSize(miniBanner, MAX_SIZE)) {
24 | return '图片体积过大'
25 | }
26 |
27 | const bucketName = `patch/${id}/banner`
28 |
29 | await uploadImageToS3(`${bucketName}/banner.avif`, banner)
30 | await uploadImageToS3(`${bucketName}/banner-mini.avif`, miniBanner)
31 | }
32 |
--------------------------------------------------------------------------------
/app/api/utils/verifyVerificationCode.ts:
--------------------------------------------------------------------------------
1 | import { getKv } from '~/lib/redis'
2 |
3 | export const verifyVerificationCode = async (
4 | email: string,
5 | userProvidedCode: string
6 | ): Promise => {
7 | const storedCode = await getKv(email)
8 |
9 | if (!storedCode) {
10 | return false
11 | }
12 |
13 | return userProvidedCode === storedCode
14 | }
15 |
--------------------------------------------------------------------------------
/app/apply/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { verifyHeaderCookie } from '~/utils/actions/verifyHeaderCookie'
4 | import { getApplyStatus } from '~/app/api/apply/status/route'
5 |
6 | export const kunGetActions = async () => {
7 | const payload = await verifyHeaderCookie()
8 |
9 | const response = await getApplyStatus(payload?.uid ?? 0)
10 | return response
11 | }
12 |
--------------------------------------------------------------------------------
/app/apply/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '申请成为创作者',
6 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`,
7 | openGraph: {
8 | title: '申请成为创作者',
9 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '申请成为创作者',
16 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/apply`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/apply/page.tsx:
--------------------------------------------------------------------------------
1 | import { ApplyContainer } from '~/components/apply/Container'
2 | import { redirect } from 'next/navigation'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from './actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const { count, role } = await kunGetActions()
13 |
14 | if (role > 1) {
15 | redirect('/apply/success')
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/app/apply/pending/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '申请已经提交 | 审核中',
6 | description: `感谢您申请成为创作者! 我们会在数小时内审核您的请求! 创作者请求正在审核中`,
7 | openGraph: {
8 | title: '申请已经提交 | 审核中',
9 | description: `感谢您申请成为创作者! 我们会在数小时内审核您的请求! 创作者请求正在审核中`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '申请已经提交 | 审核中',
16 | description: `感谢您申请成为创作者! 我们会在数小时内审核您的请求! 创作者请求正在审核中`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/apply`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/apply/pending/page.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@nextui-org/card'
2 | import { Button } from '@nextui-org/button'
3 | import Link from 'next/link'
4 | import { CheckCircle2 } from 'lucide-react'
5 | import { kunMetadata } from './metadata'
6 | import type { Metadata } from 'next'
7 |
8 | export const metadata: Metadata = kunMetadata
9 |
10 | export default function Kun() {
11 | return (
12 |
13 |
14 |
15 |
16 | 申请已经提交
17 |
18 |
感谢您申请成为创作者!
19 |
我们会在数小时内审核您的请求!
20 |
21 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/apply/success/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '申请成为创作者',
6 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`,
7 | openGraph: {
8 | title: '申请成为创作者',
9 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '申请成为创作者',
16 | description: `申请成为创作者以获得使用本站存储的权限, 自由的发布 Galgame 补丁资源, 使用最快最先进的 S3 对象存储`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/apply`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/apply/success/page.tsx:
--------------------------------------------------------------------------------
1 | import { ApplySuccess } from '~/components/apply/Success'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default function Kun() {
8 | return (
9 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/app/auth/forgot/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '忘记密码 | 找回密码',
6 | description: `忘记了您的 ${kunMoyuMoe.titleShort} 账号, 您可以通过邮箱, 用户找回密码和账号`,
7 | openGraph: {
8 | title: '忘记密码 | 找回密码',
9 | description: `忘记了您的 ${kunMoyuMoe.titleShort} 账号, 您可以通过邮箱, 用户找回密码和账号`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '忘记密码 | 找回密码',
16 | description: `忘记了您的 ${kunMoyuMoe.titleShort} 账号, 您可以通过邮箱, 用户找回密码和账号`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/auth/forgot`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/auth/forgot/page.tsx:
--------------------------------------------------------------------------------
1 | import { ForgotPassword } from '~/components/forgot/Forgot'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default function Kun() {
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/app/check-hash/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: 'BLAKE3 文件校验 | 文件完好性校验',
6 | description: `您可以输入文件的 Hash, 然后上传文件以快速使用 BLAKE3 算法检查文件完好性`,
7 | openGraph: {
8 | title: 'BLAKE3 文件校验 | 文件完好性校验',
9 | description: `您可以输入文件的 Hash, 然后上传文件以快速使用 BLAKE3 算法检查文件完好性`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: 'BLAKE3 文件校验 | 文件完好性校验',
16 | description: `您可以输入文件的 Hash, 然后上传文件以快速使用 BLAKE3 算法检查文件完好性`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/check-hash`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/check-hash/page.tsx:
--------------------------------------------------------------------------------
1 | import { CheckHashContainer } from '~/components/check-hash/Container'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default async function Kun() {
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/app/comment/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { commentSchema } from '~/validations/comment'
6 | import { getComment } from '~/app/api/comment/route'
7 |
8 | export const kunGetActions = async (params: z.infer) => {
9 | const input = safeParseSchema(commentSchema, params)
10 | if (typeof input === 'string') {
11 | return input
12 | }
13 |
14 | const response = await getComment(input)
15 | return response
16 | }
17 |
--------------------------------------------------------------------------------
/app/comment/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: 'Galgame 评论',
6 | description: `最新发布的 Galgame 评论列表, 包括对下载的看法, 对 Galgame 的评分, 对 Galgame 的吐槽等等`,
7 | openGraph: {
8 | title: 'Galgame 评论',
9 | description: `最新发布的 Galgame 评论列表, 包括对下载的看法, 对 Galgame 的评分, 对 Galgame 的吐槽等等`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: 'Galgame 评论',
16 | description: `最新发布的 Galgame 评论列表, 包括对下载的看法, 对 Galgame 的评分, 对 Galgame 的吐槽等等`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/comment`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/company/[id]/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 | import type { CompanyDetail } from '~/types/api/company'
4 |
5 | export const generateKunMetadataTemplate = (
6 | company: CompanyDetail
7 | ): Metadata => {
8 | return {
9 | title: `所属会社为 ${company.name} 的 Galgame`,
10 | description: company.introduction,
11 | openGraph: {
12 | title: `所属会社为 ${company.name} 的 Galgame`,
13 | description: company.introduction,
14 | type: 'article',
15 | publishedTime: new Date(company.created).toISOString(),
16 | modifiedTime: new Date(company.created).toISOString(),
17 | tags: company.alias
18 | },
19 | twitter: {
20 | card: 'summary',
21 | title: `所属会社为 ${company.name} 的 Galgame`,
22 | description: company.introduction
23 | },
24 | alternates: {
25 | canonical: `${kunMoyuMoe.domain.main}/company/${company.id}`
26 | },
27 | keywords: [company.name, ...company.alias]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/company/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getCompanySchema } from '~/validations/company'
6 | import { getCompany } from '~/app/api/company/all/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getCompanySchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getCompany(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/company/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import { SUPPORTED_TYPE_MAP } from '~/constants/resource'
3 | import type { Metadata } from 'next'
4 |
5 | export const kunMetadata: Metadata = {
6 | title: `Galgame 会社分类`,
7 | description: `所有的 Galgame 会社分类, ${Object.values(SUPPORTED_TYPE_MAP).toString()} 下载`,
8 | openGraph: {
9 | title: `Galgame 会社分类`,
10 | description: `所有的 Galgame 会社分类, ${Object.values(SUPPORTED_TYPE_MAP).toString()} 下载`,
11 | type: 'website',
12 | images: kunMoyuMoe.images
13 | },
14 | twitter: {
15 | card: 'summary_large_image',
16 | title: `Galgame 会社分类`,
17 | description: `所有的 Galgame 会社分类, ${Object.values(SUPPORTED_TYPE_MAP).toString()} 下载`
18 | },
19 | alternates: {
20 | canonical: `${kunMoyuMoe.domain.main}/company`
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/company/page.tsx:
--------------------------------------------------------------------------------
1 | import { Container } from '~/components/company/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { kunMetadata } from './metadata'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 100
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return (
21 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/edit/create/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '发布补丁 | 发布 Galgame',
6 | description:
7 | '您需要创建一个新 Galgame, 稍后在 Galgame 页面添加补丁资源, 如果已经有这个 Galgame 了, 直接在对应页面添加资源即可',
8 | openGraph: {
9 | title: '发布补丁 | 发布 Galgame',
10 | description:
11 | '您需要创建一个新 Galgame, 稍后在 Galgame 页面添加补丁资源, 如果已经有这个 Galgame 了, 直接在对应页面添加资源即可',
12 | type: 'website',
13 | images: kunMoyuMoe.images
14 | },
15 | twitter: {
16 | card: 'summary_large_image',
17 | title: '发布补丁 | 发布 Galgame',
18 | description:
19 | '您需要创建一个新 Galgame, 稍后在 Galgame 页面添加补丁资源, 如果已经有这个 Galgame 了, 直接在对应页面添加资源即可'
20 | },
21 | alternates: {
22 | canonical: `${kunMoyuMoe.domain.main}/edit/create`
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/edit/create/page.tsx:
--------------------------------------------------------------------------------
1 | import { CreatePatch } from '~/components/edit/create/CreatePatch'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default function Create() {
8 | return (
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/app/edit/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default async function Kun() {
4 | redirect('/edit/create')
5 | }
6 |
--------------------------------------------------------------------------------
/app/edit/rewrite/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '更改补丁 | 更改 Galgame',
6 | description:
7 | '更改已经发布的 Galgame 信息, 介绍, 标签, 会社, 别名等, 然后提出 pull request',
8 | openGraph: {
9 | title: '更改补丁 | 更改 Galgame',
10 | description:
11 | '更改已经发布的 Galgame 信息, 介绍, 标签, 会社, 别名等, 然后提出 pull request',
12 | type: 'website',
13 | images: kunMoyuMoe.images
14 | },
15 | twitter: {
16 | card: 'summary_large_image',
17 | title: '更改补丁 | 更改 Galgame',
18 | description:
19 | '更改已经发布的 Galgame 信息, 介绍, 标签, 会社, 别名等, 然后提出 pull request'
20 | },
21 | alternates: {
22 | canonical: `${kunMoyuMoe.domain.main}/edit/rewrite`
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/edit/rewrite/page.tsx:
--------------------------------------------------------------------------------
1 | import { RewritePatch } from '~/components/edit/rewrite/RewritePatch'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default async function Kun() {
8 | return (
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect } from 'react'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import toast from 'react-hot-toast'
6 |
7 | export default function Error({
8 | error,
9 | reset
10 | }: {
11 | error: Error & { digest?: string }
12 | reset: () => void
13 | }) {
14 | useEffect(() => {
15 | toast.error(error)
16 | }, [error])
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/app/friend-link/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import { kunFriends } from '~/config/friend'
3 | import type { Metadata } from 'next'
4 | import type { KunSiteImage } from '~/config/config'
5 |
6 | const friendName = kunFriends.map((f) => f.name)
7 |
8 | const friendIcons: KunSiteImage[] = kunFriends.map((f) => ({
9 | url: f.link,
10 | width: 64,
11 | height: 64,
12 | alt: f.label
13 | }))
14 |
15 | export const kunMetadata: Metadata = {
16 | title: '友情链接',
17 | description: `点击以进入 ${friendName}`,
18 | openGraph: {
19 | title: '友情链接',
20 | description: `点击以进入 ${friendName}`,
21 | type: 'website',
22 | images: friendIcons
23 | },
24 | twitter: {
25 | card: 'summary_large_image',
26 | title: '友情链接',
27 | description: `点击以进入 ${friendName}`,
28 | images: kunMoyuMoe.images
29 | },
30 | alternates: {
31 | canonical: `${kunMoyuMoe.domain.main}/friend-link`
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/friend-link/page.tsx:
--------------------------------------------------------------------------------
1 | import { KunFriendLink } from '~/components/friend-link/KunFriendLink'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default function Kun() {
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/app/galgame/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { galgameSchema } from '~/validations/galgame'
6 | import { getGalgame } from '~/app/api/galgame/route'
7 | import { getNSFWHeader } from '~/utils/actions/getNSFWHeader'
8 |
9 | export const kunGetActions = async (params: z.infer) => {
10 | const input = safeParseSchema(galgameSchema, params)
11 | if (typeof input === 'string') {
12 | return input
13 | }
14 |
15 | const nsfwEnable = await getNSFWHeader()
16 |
17 | const response = await getGalgame(input, nsfwEnable)
18 | return response
19 | }
20 |
--------------------------------------------------------------------------------
/app/message/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getMessageSchema } from '~/validations/message'
6 | import { verifyHeaderCookie } from '~/utils/actions/verifyHeaderCookie'
7 | import { getMessage } from '~/app/api/message/all/route'
8 |
9 | export const kunGetActions = async (
10 | params: z.infer
11 | ) => {
12 | const input = safeParseSchema(getMessageSchema, params)
13 | if (typeof input === 'string') {
14 | return input
15 | }
16 | const payload = await verifyHeaderCookie()
17 | if (!payload) {
18 | return '用户登陆失效'
19 | }
20 |
21 | const response = await getMessage(input, payload.uid)
22 | return response
23 | }
24 |
--------------------------------------------------------------------------------
/app/message/follow/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '资源消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/follow/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | type: 'follow',
14 | page: 1,
15 | limit: 30
16 | })
17 | if (typeof response === 'string') {
18 | return
19 | }
20 |
21 | return (
22 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/message/layout.tsx:
--------------------------------------------------------------------------------
1 | import { MessageNav } from '~/components/message/MessageNav'
2 | import { KunHeader } from '~/components/kun/Header'
3 | import { kunMetadata } from './metadata'
4 | import type { Metadata } from 'next'
5 |
6 | export const metadata: Metadata = kunMetadata
7 |
8 | export default function MessageLayout({
9 | children
10 | }: {
11 | children: React.ReactNode
12 | }) {
13 | return (
14 |
15 |
19 |
20 |
21 |
{children}
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/message/mention/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '@ 消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/mention/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | type: 'mention',
14 | page: 1,
15 | limit: 30
16 | })
17 | if (typeof response === 'string') {
18 | return
19 | }
20 |
21 | return (
22 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/message/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '用户消息',
6 | description: `这是用户消息页面, 第一次访问对应的页面会自动已读所有消息`,
7 | openGraph: {
8 | title: '用户消息',
9 | description: `这是用户消息页面, 第一次访问对应的页面会自动已读所有消息`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '用户消息',
16 | description: `这是用户消息页面, 第一次访问对应的页面会自动已读所有消息`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/message`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/message/notice/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '通知消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/notice/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({ page: 1, limit: 30 })
13 | if (typeof response === 'string') {
14 | return
15 | }
16 |
17 | return (
18 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/app/message/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default async function Kun() {
4 | redirect('/message/notice')
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/patch-resource-create/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '新补丁资源发布消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/patch-resource-create/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | type: 'patchResourceCreate',
14 | page: 1,
15 | limit: 30
16 | })
17 | if (typeof response === 'string') {
18 | return
19 | }
20 |
21 | return (
22 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/message/patch-resource-update/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '补丁资源更新消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/patch-resource-update/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | type: 'patchResourceUpdate',
14 | page: 1,
15 | limit: 30
16 | })
17 | if (typeof response === 'string') {
18 | return
19 | }
20 |
21 | return (
22 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/message/system/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 |
3 | export const kunMetadata: Metadata = {
4 | title: '系统消息'
5 | }
6 |
--------------------------------------------------------------------------------
/app/message/system/page.tsx:
--------------------------------------------------------------------------------
1 | import { MessageContainer } from '~/components/message/Container'
2 | import { ErrorComponent } from '~/components/error/ErrorComponent'
3 | import { kunMetadata } from './metadata'
4 | import { kunGetActions } from '../actions'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | type: 'system',
14 | page: 1,
15 | limit: 30
16 | })
17 | if (typeof response === 'string') {
18 | return
19 | }
20 |
21 | return (
22 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { HomeContainer } from '~/components/home/Container'
2 | import { kunGetActions } from './actions'
3 |
4 | export const revalidate = 5
5 |
6 | export default async function Kun() {
7 | const response = await kunGetActions()
8 |
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/app/patch/[id]/comment/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getPatchComment } from '~/app/api/patch/comment/get'
5 | import { getCommentVerifyStatus } from '~/app/api/admin/setting/comment/getCommentVerifyStatus'
6 | import { verifyHeaderCookie } from '~/utils/actions/verifyHeaderCookie'
7 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
8 |
9 | const patchIdSchema = z.object({
10 | patchId: z.coerce.number().min(1).max(9999999)
11 | })
12 |
13 | export const kunGetCommentActions = async (
14 | params: z.infer
15 | ) => {
16 | const input = safeParseSchema(patchIdSchema, params)
17 | if (typeof input === 'string') {
18 | return input
19 | }
20 | const payload = await verifyHeaderCookie()
21 |
22 | const response = await getPatchComment(input, payload?.uid ?? 0)
23 | return response
24 | }
25 |
26 | export const kunGetCommentVerifyStatusActions = async () => {
27 | const response = await getCommentVerifyStatus()
28 | return response
29 | }
30 |
--------------------------------------------------------------------------------
/app/patch/[id]/history/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getPatchHistorySchema } from '~/validations/patch'
5 | import { getPatchHistory } from '~/app/api/patch/history/route'
6 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getPatchHistorySchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getPatchHistory(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/patch/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default async function Kun({
4 | params
5 | }: {
6 | params: Promise<{ id: string }>
7 | }) {
8 | const { id } = await params
9 | redirect(`/patch/${id}/introduction`)
10 | }
11 |
--------------------------------------------------------------------------------
/app/patch/[id]/pr/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getPullRequest } from '~/app/api/patch/pr/route'
6 |
7 | const patchIdSchema = z.object({
8 | patchId: z.coerce.number().min(1).max(9999999)
9 | })
10 |
11 | export const kunGetActions = async (params: z.infer) => {
12 | const input = safeParseSchema(patchIdSchema, params)
13 | if (typeof input === 'string') {
14 | return input
15 | }
16 |
17 | const response = await getPullRequest(input)
18 | return response
19 | }
20 |
--------------------------------------------------------------------------------
/app/patch/[id]/resource/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { verifyHeaderCookie } from '~/utils/actions/verifyHeaderCookie'
6 | import { getPatchResource } from '~/app/api/patch/resource/get'
7 |
8 | const patchIdSchema = z.object({
9 | patchId: z.coerce.number().min(1).max(9999999)
10 | })
11 |
12 | export const kunGetActions = async (params: z.infer) => {
13 | const input = safeParseSchema(patchIdSchema, params)
14 | if (typeof input === 'string') {
15 | return input
16 | }
17 | const payload = await verifyHeaderCookie()
18 |
19 | const response = await getPatchResource(input, payload?.uid ?? 0)
20 | return response
21 | }
22 |
--------------------------------------------------------------------------------
/app/patch/[id]/walkthrough/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getWalkthrough } from '~/app/api/patch/walkthrough/get'
6 |
7 | const patchIdSchema = z.object({
8 | patchId: z.coerce.number().min(1).max(9999999)
9 | })
10 |
11 | export const kunGetActions = async (params: z.infer) => {
12 | const input = safeParseSchema(patchIdSchema, params)
13 | if (typeof input === 'string') {
14 | return input
15 | }
16 |
17 | const response = await getWalkthrough(input)
18 | return response
19 | }
20 |
--------------------------------------------------------------------------------
/app/providers.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { AppProgressBar, useRouter } from 'next-nprogress-bar'
4 | import { NextUIProvider } from '@nextui-org/react'
5 | import { ThemeProvider } from 'next-themes'
6 |
7 | export const Providers = ({ children }: { children: React.ReactNode }) => {
8 | const router = useRouter()
9 |
10 | return (
11 |
12 | {children}
13 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/ranking/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default function Kun() {
4 | redirect('/ranking/user')
5 | }
6 |
--------------------------------------------------------------------------------
/app/ranking/patch/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: 'Galgame 补丁排行',
6 | description: `所有的 Galgame 以及 Galgame 补丁资源排行, 按照 浏览数, 下载数, 点赞数 筛选以获得最优质, 最热门, 最好的 Galgame 补丁资源`,
7 | openGraph: {
8 | title: 'Galgame 补丁排行',
9 | description: `所有的 Galgame 以及 Galgame 补丁资源排行, 按照 浏览数, 下载数, 点赞数 筛选以获得最优质, 最热门, 最好的 Galgame 补丁资源`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: 'Galgame 补丁排行',
16 | description: `所有的 Galgame 以及 Galgame 补丁资源排行, 按照 浏览数, 下载数, 点赞数 筛选以获得最优质, 最热门, 最好的 Galgame 补丁资源`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/ranking/patch`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/ranking/patch/page.tsx:
--------------------------------------------------------------------------------
1 | import { getPatchRanking } from '~/app/ranking/actions'
2 | import { RankingContainer } from '~/components/ranking/Container'
3 | import { PatchList } from '~/components/ranking/patch/PatchList'
4 | import { kunMetadata } from './metadata'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | interface Props {
12 | searchParams: Promise<{ sortBy?: string; timeRange?: string }>
13 | }
14 |
15 | export default async function Kun({ searchParams }: Props) {
16 | const res = await searchParams
17 |
18 | const sortBy = res.sortBy || 'view'
19 | const timeRange = res.timeRange || 'all'
20 | const patches = await getPatchRanking(sortBy, timeRange)
21 |
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/ranking/user/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '用户排行榜',
6 | description: `所有发布 Galgame 以及 Galgame 资源的用户排行, 可以按照 萌萌点, Galgame, Galgame 补丁, 评论 进行排序, 查看用户的活跃和贡献排行, 查看最值得信赖的用户, 查看最强的 Galgame 补丁创作者`,
7 | openGraph: {
8 | title: '用户排行榜',
9 | description: `所有发布 Galgame 以及 Galgame 资源的用户排行, 可以按照 萌萌点, Galgame, Galgame 补丁, 评论 进行排序, 查看用户的活跃和贡献排行, 查看最值得信赖的用户, 查看最强的 Galgame 补丁创作者`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: '用户排行榜',
16 | description: `所有发布 Galgame 以及 Galgame 资源的用户排行, 可以按照 萌萌点, Galgame, Galgame 补丁, 评论 进行排序, 查看用户的活跃和贡献排行, 查看最值得信赖的用户, 查看最强的 Galgame 补丁创作者`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/ranking/patch`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/ranking/user/page.tsx:
--------------------------------------------------------------------------------
1 | import { getUserRanking } from '~/app/ranking/actions'
2 | import { RankingContainer } from '~/components/ranking/Container'
3 | import { UserList } from '~/components/ranking/user/UserList'
4 | import { kunMetadata } from './metadata'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | interface Props {
12 | searchParams: Promise<{ sortBy?: string; timeRange?: string }>
13 | }
14 |
15 | export default async function Kun({ searchParams }: Props) {
16 | const res = await searchParams
17 |
18 | const sortBy = res.sortBy || 'moemoepoint'
19 | const timeRange = res.timeRange || 'all'
20 | const users = await getUserRanking(sortBy)
21 |
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/release/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getReleaseSchema } from '~/validations/release'
6 | import { getGalgameRelease } from '~/app/api/release/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getReleaseSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getGalgameRelease(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/release/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: 'Galgame 新作时间表',
6 | description: `本月发售的最新 Galgame 列表, Galgame 新作, Galgame 时间表, 最新 Galgame 新作分类, Galgame 新作资源下载, Galgame 新作大全`,
7 | openGraph: {
8 | title: 'Galgame 新作时间表',
9 | description: `本月发售的最新 Galgame 列表, Galgame 新作, Galgame 时间表, 最新 Galgame 新作分类, Galgame 新作资源下载, Galgame 新作大全`,
10 | type: 'website',
11 | images: kunMoyuMoe.images
12 | },
13 | twitter: {
14 | card: 'summary_large_image',
15 | title: 'Galgame 新作时间表',
16 | description: `本月发售的最新 Galgame 列表, Galgame 新作, Galgame 时间表, 最新 Galgame 新作分类, Galgame 新作资源下载, Galgame 新作大全`
17 | },
18 | alternates: {
19 | canonical: `${kunMoyuMoe.domain.main}/release`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/release/page.tsx:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 | import { ReleaseContainer } from '~/components/release/Container'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import { kunMetadata } from './metadata'
6 | import type { Metadata } from 'next'
7 |
8 | export const revalidate = 5
9 |
10 | export const metadata: Metadata = kunMetadata
11 |
12 | export default async function Kun() {
13 | const currentYear = dayjs().year()
14 | const currentMonth = dayjs().month() + 1
15 |
16 | const response = await kunGetActions({
17 | year: currentYear,
18 | month: currentMonth
19 | })
20 | if (typeof response === 'string') {
21 | return
22 | }
23 |
24 | return (
25 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/app/resource/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { resourceSchema } from '~/validations/resource'
6 | import { getPatchResource } from '~/app/api/resource/route'
7 | import { getNSFWHeader } from '~/utils/actions/getNSFWHeader'
8 |
9 | export const kunGetActions = async (params: z.infer) => {
10 | const input = safeParseSchema(resourceSchema, params)
11 | if (typeof input === 'string') {
12 | return input
13 | }
14 |
15 | const nsfwEnable = await getNSFWHeader()
16 |
17 | const response = await getPatchResource(input, nsfwEnable)
18 | return response
19 | }
20 |
--------------------------------------------------------------------------------
/app/search/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import { SUPPORTED_TYPE_MAP } from '~/constants/resource'
3 | import type { Metadata } from 'next'
4 |
5 | export const kunMetadata: Metadata = {
6 | title: '搜索 Galgame 补丁',
7 | description: `您可以在此处搜索所有的 Galgame 补丁资源, 包括 ${Object.values(SUPPORTED_TYPE_MAP)} 等等`,
8 | openGraph: {
9 | title: '搜索 Galgame 补丁',
10 | description: `您可以在此处搜索所有的 Galgame 补丁资源, 包括 ${Object.values(SUPPORTED_TYPE_MAP)} 等等`,
11 | type: 'website',
12 | images: kunMoyuMoe.images
13 | },
14 | twitter: {
15 | card: 'summary_large_image',
16 | title: '搜索 Galgame 补丁',
17 | description: `您可以在此处搜索所有的 Galgame 补丁资源, 包括 ${Object.values(SUPPORTED_TYPE_MAP)} 等等`
18 | },
19 | alternates: {
20 | canonical: `${kunMoyuMoe.domain.main}/search`
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/search/page.tsx:
--------------------------------------------------------------------------------
1 | import { SearchPage } from '~/components/search/Container'
2 | import { kunMetadata } from './metadata'
3 | import { Suspense } from 'react'
4 | import type { Metadata } from 'next'
5 |
6 | export const metadata: Metadata = kunMetadata
7 |
8 | export default function Search() {
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/app/settings/user/metadata.ts:
--------------------------------------------------------------------------------
1 | import { kunMoyuMoe } from '~/config/moyu-moe'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | title: '用户信息设置',
6 | description:
7 | '您可以在此处任何更改您的个人信息, 头像, 签名, 用户名, 更改密码, 更改邮箱',
8 | openGraph: {
9 | title: '用户信息设置',
10 | description:
11 | '您可以在此处任何更改您的个人信息, 头像, 签名, 用户名, 更改密码, 更改邮箱',
12 | type: 'website',
13 | images: kunMoyuMoe.images
14 | },
15 | twitter: {
16 | card: 'summary_large_image',
17 | title: '用户信息设置',
18 | description:
19 | '您可以在此处任何更改您的个人信息, 头像, 签名, 用户名, 更改密码, 更改邮箱'
20 | },
21 | alternates: {
22 | canonical: `${kunMoyuMoe.domain.main}/settings/user`
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/settings/user/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserSettings } from '~/components/settings/user/Container'
2 | import { kunMetadata } from './metadata'
3 | import type { Metadata } from 'next'
4 |
5 | export const metadata: Metadata = kunMetadata
6 |
7 | export default function User() {
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/app/tag/[id]/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 | import type { Metadata } from 'next'
3 | import type { TagDetail } from '~/types/api/tag'
4 |
5 | export const generateKunMetadataTemplate = (tag: TagDetail): Metadata => {
6 | return {
7 | ...generateNullMetadata(`标签 - ${tag.name}`)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/tag/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
5 | import { getTagSchema } from '~/validations/tag'
6 | import { getTag } from '~/app/api/tag/all/route'
7 |
8 | export const kunGetActions = async (params: z.infer) => {
9 | const input = safeParseSchema(getTagSchema, params)
10 | if (typeof input === 'string') {
11 | return input
12 | }
13 |
14 | const response = await getTag(input)
15 | return response
16 | }
17 |
--------------------------------------------------------------------------------
/app/tag/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 | import type { Metadata } from 'next'
3 |
4 | export const kunMetadata: Metadata = {
5 | ...generateNullMetadata(`Galgame 补丁标签`)
6 | }
7 |
--------------------------------------------------------------------------------
/app/tag/page.tsx:
--------------------------------------------------------------------------------
1 | import { Container } from '~/components/tag/Container'
2 | import { kunMetadata } from './metadata'
3 | import { kunGetActions } from './actions'
4 | import { ErrorComponent } from '~/components/error/ErrorComponent'
5 | import type { Metadata } from 'next'
6 |
7 | export const revalidate = 5
8 |
9 | export const metadata: Metadata = kunMetadata
10 |
11 | export default async function Kun() {
12 | const response = await kunGetActions({
13 | page: 1,
14 | limit: 100
15 | })
16 | if (typeof response === 'string') {
17 | return
18 | }
19 |
20 | return
21 | }
22 |
--------------------------------------------------------------------------------
/app/user/[id]/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { verifyHeaderCookie } from '~/utils/actions/verifyHeaderCookie'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserProfile } from '~/app/api/user/status/info/route'
7 |
8 | const getProfileSchema = z.object({
9 | id: z.coerce.number().min(1).max(9999999)
10 | })
11 |
12 | export const kunGetUserStatusActions = async (id: number) => {
13 | const input = safeParseSchema(getProfileSchema, { id })
14 | if (typeof input === 'string') {
15 | return input
16 | }
17 | const payload = await verifyHeaderCookie()
18 |
19 | const user = await getUserProfile(input, payload?.uid ?? 0)
20 | return user
21 | }
22 |
--------------------------------------------------------------------------------
/app/user/[id]/comment/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getUserInfoSchema } from '~/validations/user'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserComment } from '~/app/api/user/profile/comment/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getUserInfoSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserComment(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/user/[id]/comment/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('评论')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/comment/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserComment } from '~/components/user/comment/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { generateKunMetadataTemplate } from './metadata'
5 |
6 | export const revalidate = 5
7 |
8 | interface Props {
9 | params: Promise<{ id: string }>
10 | }
11 |
12 | export const generateMetadata = () => {
13 | return generateKunMetadataTemplate
14 | }
15 |
16 | export default async function Kun({ params }: Props) {
17 | const { id } = await params
18 |
19 | const response = await kunGetActions({
20 | uid: Number(id),
21 | page: 1,
22 | limit: 20
23 | })
24 | if (typeof response === 'string') {
25 | return
26 | }
27 |
28 | return (
29 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/user/[id]/contribute/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getUserInfoSchema } from '~/validations/user'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserContribute } from '~/app/api/user/profile/contribute/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getUserInfoSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserContribute(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/user/[id]/contribute/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('贡献过的 Galgame')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/contribute/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserContribute } from '~/components/user/contribute/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { generateKunMetadataTemplate } from './metadata'
5 |
6 | export const revalidate = 5
7 |
8 | interface Props {
9 | params: Promise<{ id: string }>
10 | }
11 |
12 | export const generateMetadata = () => {
13 | return generateKunMetadataTemplate
14 | }
15 |
16 | export default async function Kun({ params }: Props) {
17 | const { id } = await params
18 |
19 | const response = await kunGetActions({
20 | uid: Number(id),
21 | page: 1,
22 | limit: 20
23 | })
24 | if (typeof response === 'string') {
25 | return
26 | }
27 |
28 | return (
29 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/user/[id]/favorite/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getUserInfoSchema } from '~/validations/user'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserFavorite } from '~/app/api/user/profile/favorite/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getUserInfoSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserFavorite(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/user/[id]/favorite/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('收藏补丁')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/favorite/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserFavorite } from '~/components/user/favorite/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { generateKunMetadataTemplate } from './metadata'
5 |
6 | export const revalidate = 5
7 |
8 | interface Props {
9 | params: Promise<{ id: string }>
10 | }
11 |
12 | export const generateMetadata = () => {
13 | return generateKunMetadataTemplate
14 | }
15 |
16 | export default async function Kun({ params }: Props) {
17 | const { id } = await params
18 |
19 | const response = await kunGetActions({
20 | uid: Number(id),
21 | page: 1,
22 | limit: 20
23 | })
24 | if (typeof response === 'string') {
25 | return
26 | }
27 |
28 | return (
29 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/user/[id]/galgame/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getUserInfoSchema } from '~/validations/user'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserGalgame } from '~/app/api/user/profile/galgame/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getUserInfoSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserGalgame(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/user/[id]/galgame/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('Galgame 项目')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/galgame/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserGalgame } from '~/components/user/galgame/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { generateKunMetadataTemplate } from './metadata'
5 |
6 | export const revalidate = 5
7 |
8 | interface Props {
9 | params: Promise<{ id: string }>
10 | }
11 |
12 | export const generateMetadata = () => {
13 | return generateKunMetadataTemplate
14 | }
15 |
16 | export default async function Kun({ params }: Props) {
17 | const { id } = await params
18 |
19 | const response = await kunGetActions({
20 | uid: Number(id),
21 | page: 1,
22 | limit: 20
23 | })
24 | if (typeof response === 'string') {
25 | return
26 | }
27 |
28 | return (
29 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/user/[id]/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('用户主页')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default async function Kun({
4 | params
5 | }: {
6 | params: Promise<{ id: string }>
7 | }) {
8 | const { id } = await params
9 | redirect(`/user/${id}/resource`)
10 | }
11 |
--------------------------------------------------------------------------------
/app/user/[id]/resource/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { z } from 'zod'
4 | import { getUserInfoSchema } from '~/validations/user'
5 | import { safeParseSchema } from '~/utils/actions/safeParseSchema'
6 | import { getUserPatchResource } from '~/app/api/user/profile/resource/route'
7 |
8 | export const kunGetActions = async (
9 | params: z.infer
10 | ) => {
11 | const input = safeParseSchema(getUserInfoSchema, params)
12 | if (typeof input === 'string') {
13 | return input
14 | }
15 |
16 | const response = await getUserPatchResource(input)
17 | return response
18 | }
19 |
--------------------------------------------------------------------------------
/app/user/[id]/resource/metadata.ts:
--------------------------------------------------------------------------------
1 | import { generateNullMetadata } from '~/utils/noIndex'
2 |
3 | export const generateKunMetadataTemplate = {
4 | ...generateNullMetadata('补丁资源')
5 | }
6 |
--------------------------------------------------------------------------------
/app/user/[id]/resource/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserResource } from '~/components/user/resource/Container'
2 | import { kunGetActions } from './actions'
3 | import { ErrorComponent } from '~/components/error/ErrorComponent'
4 | import { generateKunMetadataTemplate } from './metadata'
5 |
6 | export const revalidate = 5
7 |
8 | interface Props {
9 | params: Promise<{ id: string }>
10 | }
11 |
12 | export const generateMetadata = () => {
13 | return generateKunMetadataTemplate
14 | }
15 |
16 | export default async function Kun({ params }: Props) {
17 | const { id } = await params
18 |
19 | const response = await kunGetActions({
20 | uid: Number(id),
21 | page: 1,
22 | limit: 20
23 | })
24 | if (typeof response === 'string') {
25 | return
26 | }
27 |
28 | return (
29 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/user/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation'
2 |
3 | export default async function Kun() {
4 | redirect('/')
5 | }
6 |
--------------------------------------------------------------------------------
/components/about/Header.tsx:
--------------------------------------------------------------------------------
1 | // import { Input } from '@nextui-org/react'
2 | // import { Search } from 'lucide-react'
3 | import { KunHeader } from '../kun/Header'
4 |
5 | // interface HeaderProps {
6 | // onSearch: (value: string) => void
7 | // }
8 |
9 | export const KunAboutHeader = () => {
10 | return (
11 |
12 |
13 |
14 | {/* }
21 | onChange={(e) => onSearch(e.target.value)}
22 | /> */}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/components/about/SidebarContent.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { KunTreeNode } from '~/lib/mdx/types'
4 | import { TreeItem } from './SideTreeItem'
5 |
6 | interface Props {
7 | tree: KunTreeNode
8 | }
9 |
10 | export const SidebarContent = ({ tree }: Props) => {
11 | return (
12 |
13 | {tree.type === 'directory' &&
14 | tree.children?.map((child, index) => (
15 |
16 | ))}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/components/admin/Navbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {
4 | NavbarContent,
5 | NavbarItem,
6 | Navbar as NextUINavbar
7 | } from '@nextui-org/react'
8 |
9 | export const Navbar = () => {
10 | return (
11 |
12 |
13 | 哦哈呦~
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/components/admin/setting/Container.tsx:
--------------------------------------------------------------------------------
1 | import { DisableRegisterSetting } from './DisableRegisterSetting'
2 | import { EnableCommentVerify } from './EnableCommentVerify'
3 | import { EnableOnlyCreatorCreateGalgame } from './EnableOnlyCreatorCreateGalgame'
4 |
5 | interface Props {
6 | disableRegister: boolean
7 | enableCommentVerify: boolean
8 | enableOnlyCreatorCreate: boolean
9 | }
10 |
11 | export const AdminSetting = ({
12 | disableRegister,
13 | enableCommentVerify,
14 | enableOnlyCreatorCreate
15 | }: Props) => {
16 | return (
17 |
18 |
19 |
网站设置
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/components/admin/stats/KunStats.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Divider } from '@nextui-org/react'
3 | // import { KunAdminLineChart } from './KunAdminLineChart'
4 | import { KunAdminStatistic } from './KunAdminStatistic'
5 |
6 | export const KunStats: FC = () => {
7 | return (
8 |
9 |
10 |
11 | {/*
*/}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/components/admin/stats/StatsCard.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Card, CardBody } from '@nextui-org/react'
4 | import type { FC } from 'react'
5 |
6 | export const StatsCard: FC<{ title: string; value: number }> = ({
7 | title,
8 | value
9 | }) => (
10 |
11 |
12 |
13 | {title}
14 |
15 |
16 | {value.toLocaleString()}
17 |
18 |
19 |
20 | )
21 |
--------------------------------------------------------------------------------
/components/check-hash/Container.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from 'react'
2 | import { CheckHash } from './CheckHash'
3 | import { KunHeader } from '../kun/Header'
4 |
5 | export const CheckHashContainer = () => {
6 | return (
7 |
8 |
9 |
13 |
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/components/comment/_sort.d.ts:
--------------------------------------------------------------------------------
1 | export type SortOption = 'created' | 'like'
2 | export type SortDirection = 'asc' | 'desc'
3 |
--------------------------------------------------------------------------------
/components/company/CompanyList.tsx:
--------------------------------------------------------------------------------
1 | import { KunMasonryGrid } from '~/components/kun/MasonryGrid'
2 | import { KunLoading } from '~/components/kun/Loading'
3 | import { KunNull } from '~/components/kun/Null'
4 | import { CompanyCard } from './Card'
5 | import type { FC } from 'react'
6 | import type { Company as CompanyType } from '~/types/api/company'
7 |
8 | interface CompanyListProps {
9 | companies: CompanyType[]
10 | loading: boolean
11 | searching: boolean
12 | }
13 |
14 | export const CompanyList: FC = ({
15 | companies,
16 | loading,
17 | searching
18 | }) => {
19 | if (loading) {
20 | return
21 | }
22 |
23 | if (!searching && companies.length === 0) {
24 | return
25 | }
26 |
27 | return (
28 |
29 | {companies.map((company) => (
30 |
31 | ))}
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/components/company/form/LogoImage.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { dataURItoBlob } from '~/utils/dataURItoBlob'
4 | import { KunImageCropper } from '~/components/kun/cropper/KunImageCropper'
5 | import type { FC } from 'react'
6 |
7 | interface Props {
8 | initialUrl: string
9 | setInitialUrl: (url: string) => void
10 | setImageBlob: (imageBlob: Blob | null) => void
11 | }
12 |
13 | export const LogoImage: FC = ({
14 | initialUrl,
15 | setInitialUrl,
16 | setImageBlob
17 | }) => {
18 | const handleImageComplete = async (croppedImage: string) => {
19 | const imageBlob = dataURItoBlob(croppedImage)
20 | setInitialUrl('')
21 | setImageBlob(imageBlob)
22 | }
23 |
24 | const removeImage = () => {
25 | setInitialUrl('')
26 | setImageBlob(null)
27 | }
28 |
29 | return (
30 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/components/edit/VNDB.d.ts:
--------------------------------------------------------------------------------
1 | export interface Title {
2 | title: string
3 | lang: 'ja' | 'zh-Hans' | 'en'
4 | }
5 |
6 | export interface VNDBData {
7 | id: number
8 | title: string
9 | titles: Title[]
10 | aliases: string[]
11 | languages: string[]
12 | platforms: string[]
13 | description: string
14 | }
15 |
16 | export interface VNDB {
17 | title: string
18 | titles: Title[]
19 | description: string
20 | aliases: string[]
21 | released: string
22 | }
23 |
24 | export interface VNDBResponse {
25 | more: boolean
26 | results: VNDB[]
27 | }
28 |
--------------------------------------------------------------------------------
/components/edit/rewrite/GameNameInput.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@nextui-org/input'
2 |
3 | interface Props {
4 | name: string
5 | onChange: (newName: string) => void
6 | error?: string
7 | }
8 |
9 | export const GameNameInput = ({ name, onChange, error }: Props) => (
10 |
11 |
游戏名称 (必须)
12 | onChange(e.target.value)}
20 | isInvalid={!!error}
21 | errorMessage={error}
22 | />
23 |
24 | )
25 |
--------------------------------------------------------------------------------
/components/edit/rewrite/VNDBInput.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Input } from '@nextui-org/react'
4 |
5 | interface Props {
6 | vndbId: string
7 | setVNDBId: (vndbId: string) => void
8 | errors?: string
9 | }
10 |
11 | export const VNDBInput = ({ vndbId, setVNDBId, errors }: Props) => {
12 | return (
13 |
14 |
VNDB ID (可选)
15 |
setVNDBId(e.target.value)}
21 | isInvalid={!!errors}
22 | errorMessage={errors}
23 | />
24 |
25 | 提示: VNDB ID 需要 VNDB 官网 (vndb.org)
26 | 获取,当进入对应游戏的页面,游戏页面的 URL (形如
27 | https://vndb.org/v19658) 中的 v19658 就是 VNDB ID
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/components/galgame/_sort.d.ts:
--------------------------------------------------------------------------------
1 | export type SortOption =
2 | | 'resource_update_time'
3 | | 'created'
4 | | 'view'
5 | | 'download'
6 | export type SortDirection = 'asc' | 'desc'
7 |
--------------------------------------------------------------------------------
/components/home/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { KunCarousel } from './carousel/KunCarousel'
2 | import { getKunPosts } from './carousel/mdx'
3 |
4 | export const HomeHero = () => {
5 | const posts = getKunPosts()
6 |
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/components/kun/BackToTop.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Button } from '@nextui-org/react'
4 | import { ArrowUp } from 'lucide-react'
5 | import { useEffect, useState } from 'react'
6 |
7 | export const KunBackToTop = () => {
8 | const [show, setShow] = useState(false)
9 |
10 | useEffect(() => {
11 | const handleScroll = () => {
12 | setShow(window.scrollY > 400)
13 | }
14 |
15 | window.addEventListener('scroll', handleScroll)
16 | return () => window.removeEventListener('scroll', handleScroll)
17 | }, [])
18 |
19 | const scrollToTop = () => {
20 | window.scrollTo({
21 | top: 0,
22 | behavior: 'smooth'
23 | })
24 | }
25 |
26 | if (!show) {
27 | return null
28 | }
29 |
30 | return (
31 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/components/kun/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { Spinner } from '@nextui-org/spinner'
2 | import { cn } from '~/utils/cn'
3 |
4 | interface Props {
5 | hint: string
6 | className?: string
7 | }
8 |
9 | export const KunLoading = ({ hint, className }: Props) => {
10 | return (
11 |
17 |
18 | {hint}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/components/kun/NSFWIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { Alert } from '@nextui-org/alert'
2 |
3 | interface Props {
4 | isNSFWEnable: boolean
5 | }
6 |
7 | export const NSFWIndicator = ({ isNSFWEnable }: Props) => {
8 | const titleString = isNSFWEnable ? 'NSFW 已启用' : 'NSFW 未启用'
9 | const description = isNSFWEnable
10 | ? '网站已启用 NSFW, 请注意您周围没有人'
11 | : '网站未启用 NSFW, 部分补丁内容不可见, 请在网站右上角打开 NSFW'
12 |
13 | return (
14 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/components/kun/Null.tsx:
--------------------------------------------------------------------------------
1 | import { randomNum } from '~/utils/random'
2 | import { useEffect, useState } from 'react'
3 | import { KunLoading } from './Loading'
4 | import Image from 'next/image'
5 |
6 | interface Props {
7 | message: string
8 | }
9 |
10 | export const KunNull = ({ message }: Props) => {
11 | const [stickerSrc, setStickerSrc] = useState('')
12 |
13 | useEffect(() => {
14 | const randomPackIndex = randomNum(1, 5)
15 | const randomStickerIndex = randomNum(1, 80)
16 | setStickerSrc(
17 | `https://sticker.kungal.com/stickers/KUNgal${randomPackIndex}/${randomStickerIndex}.webp`
18 | )
19 | }, [])
20 |
21 | if (!stickerSrc) {
22 | return
23 | }
24 |
25 | return (
26 |
27 |
35 | {message}
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/components/kun/TextDivider.tsx:
--------------------------------------------------------------------------------
1 | import { Divider } from '@nextui-org/divider'
2 |
3 | interface Props {
4 | text: string
5 | }
6 |
7 | export const KunTextDivider = ({ text }: Props) => {
8 | return (
9 |
10 |
11 |
{text}
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/components/kun/auth/captcha.d.ts:
--------------------------------------------------------------------------------
1 | export interface KunCaptchaImage {
2 | id: string
3 | data: string
4 | isWhiteHair: boolean
5 | }
6 |
7 | export interface AuthFormData {
8 | email: string
9 | password: string
10 | }
11 |
12 | export interface CaptchaResponse {
13 | images: KunCaptchaImage[]
14 | sessionId: string
15 | }
16 |
17 | export interface CaptchaVerifyRequest {
18 | sessionId: string
19 | selectedIds: string[]
20 | }
21 |
22 | export interface CaptchaVerifyResponse {
23 | success: boolean
24 | message: string
25 | }
26 |
--------------------------------------------------------------------------------
/components/kun/cropper/KunMosaicController.tsx:
--------------------------------------------------------------------------------
1 | import { Slider } from '@nextui-org/react'
2 | import { FC } from 'react'
3 |
4 | interface KunMosaicControllerProps {
5 | mosaicSize: number
6 | onMosaicSizeChange: (size: number) => void
7 | }
8 |
9 | export const KunMosaicController: FC = ({
10 | mosaicSize,
11 | onMosaicSizeChange
12 | }) => {
13 | return (
14 |
15 |
16 |
17 | onMosaicSizeChange(Number(value))}
24 | className="max-w-md"
25 | label="单位:像素"
26 | />
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/components/kun/cropper/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface KunAspect {
2 | x: number
3 | y: number
4 | }
5 |
--------------------------------------------------------------------------------
/components/kun/floating-card/KunUserStatCard.tsx:
--------------------------------------------------------------------------------
1 | interface UserStatCardProps {
2 | value: number
3 | label: string
4 | }
5 |
6 | export const KunUserStatCard = ({ value, label }: UserStatCardProps) => {
7 | return (
8 |
9 |
{value.toLocaleString()}
10 |
{label}
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/components/kun/icons/GitHub.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { SVGProps } from 'react'
3 |
4 | export const GitHub = (props: SVGProps) => {
5 | return (
6 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/components/kun/icons/Markdown.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { SVGProps } from 'react'
3 |
4 | export const Markdown = (props: SVGProps) => {
5 | return (
6 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/components/kun/icons/Telegram.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { SVGProps } from 'react'
3 |
4 | export const Telegram = (props: SVGProps) => {
5 | return (
6 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/components/kun/milkdown/Editor.tsx:
--------------------------------------------------------------------------------
1 | import { KunEditorProvider } from './EditorProvider'
2 | import { MilkdownProvider } from '@milkdown/react'
3 | import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/react'
4 |
5 | interface KunEditorProps {
6 | valueMarkdown: string
7 | saveMarkdown: (markdown: string) => void
8 | placeholder?: string
9 | }
10 |
11 | export const KunEditor = (props: KunEditorProps) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/components/kun/milkdown/plugins/MenuButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Tooltip } from '@nextui-org/react'
2 | import { FC } from 'react'
3 | import { LucideIcon } from 'lucide-react'
4 |
5 | interface MenuButtonProps {
6 | tooltip: string
7 | icon: LucideIcon
8 | onPress: () => void
9 | ariaLabel: string
10 | }
11 |
12 | export const MenuButton: FC = ({
13 | tooltip,
14 | icon: Icon,
15 | onPress,
16 | ariaLabel
17 | }) => (
18 |
19 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/components/kun/top-bar/Search.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Tooltip } from '@nextui-org/tooltip'
4 | import { Button, Link } from '@nextui-org/react'
5 | import { Search } from 'lucide-react'
6 | import { useRouter } from 'next-nprogress-bar'
7 | import { useHotkeys } from 'react-hotkeys-hook'
8 |
9 | export const KunSearch = () => {
10 | const router = useRouter()
11 | useHotkeys('ctrl+k', (event) => {
12 | event.preventDefault()
13 | router.push('/search')
14 | })
15 |
16 | return (
17 |
23 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/components/kun/utils/loli.ts:
--------------------------------------------------------------------------------
1 | import { randomNum } from '~/utils/random'
2 |
3 | const getAssetsFile = (name: string) => `/sooner/${name}.webp`
4 |
5 | const number = randomNum(0, 3)
6 |
7 | let loli = ''
8 | let name = ''
9 |
10 | if (number === 0) {
11 | // Actually, her full name is: アーデルハイト・フォン・ベルクシュトラーセ
12 | name = 'あーちゃん'
13 | loli = getAssetsFile(name)
14 | } else if (number === 1) {
15 | name = 'こじかひわ'
16 | loli = getAssetsFile(name)
17 | } else if (number === 2) {
18 | name = '雪々'
19 | loli = getAssetsFile(name)
20 | } else {
21 | name = '琥珀'
22 | loli = getAssetsFile(name)
23 | }
24 |
25 | export const loliAttribute = { loli, name }
26 |
--------------------------------------------------------------------------------
/components/patch/Contributor.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardBody, CardHeader } from '@nextui-org/card'
2 | import { AvatarGroup } from '@nextui-org/avatar'
3 | import { KunAvatar } from '~/components/kun/floating-card/KunAvatar'
4 |
5 | interface Props {
6 | users: KunUser[]
7 | }
8 |
9 | export const PatchContributor = ({ users }: Props) => {
10 | return (
11 |
12 |
13 | 贡献者
14 |
15 |
16 | 感谢下面的朋友们为本补丁信息做出的贡献
17 |
18 | {users.map((user) => (
19 |
28 | ))}
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/components/patch/_diff.scss:
--------------------------------------------------------------------------------
1 | .kun-content-added {
2 | display: inline;
3 | color: #17c964;
4 | font-weight: bold;
5 | }
6 |
7 | .kun-content-deleted {
8 | display: inline;
9 | color: #f31260;
10 | text-decoration: line-through;
11 | font-weight: bold;
12 | }
13 |
14 | .kun-content-modified {
15 | display: inline;
16 | }
17 |
--------------------------------------------------------------------------------
/components/patch/comment/_scrollIntoComment.ts:
--------------------------------------------------------------------------------
1 | export const scrollIntoComment = (id: number | null) => {
2 | if (id === null) {
3 | return
4 | }
5 |
6 | const targetComment = document.getElementById(`comment-${id}`)
7 | if (targetComment) {
8 | targetComment.scrollIntoView({ behavior: 'smooth', block: 'center' })
9 | targetComment.classList.add('bg-default-100')
10 | setTimeout(() => {
11 | targetComment.classList.remove('bg-default-100')
12 | }, 2000)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/components/patch/introduction/Container.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardBody, CardHeader } from '@nextui-org/card'
2 | import { Info } from './Info'
3 | import { PatchTag } from './Tag'
4 | import { PatchCompany } from './Company'
5 | import type { PatchIntroduction } from '~/types/api/patch'
6 |
7 | interface Props {
8 | intro: PatchIntroduction
9 | patchId: number
10 | }
11 |
12 | export const InfoContainer = ({ intro, patchId }: Props) => {
13 | return (
14 |
15 |
16 | 游戏介绍
17 |
18 |
19 |
20 |
21 | {/*
22 |
游戏制作商
23 | */}
24 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/components/patch/resource/share.d.ts:
--------------------------------------------------------------------------------
1 | import type { Control, FieldErrors } from 'react-hook-form'
2 | import type { PatchResourceLink } from '~/types/api/patch'
3 |
4 | interface Fields {
5 | type: string[]
6 | name: string
7 | modelName: string
8 | patchId: number
9 | code: string
10 | storage: string
11 | hash: string
12 | content: string
13 | size: string
14 | password: string
15 | note: string
16 | language: string[]
17 | platform: string[]
18 | }
19 |
20 | // uploadStatus: 1 - uploading, 2 - merging, 3 - complete, 4 - error
21 | export interface FileStatus {
22 | file: File
23 | progress: number
24 | uploadStatus: number
25 | error?: string
26 | hash?: string
27 | filetype?: string
28 | }
29 |
30 | export type ErrorType = FieldErrors
31 | export type ControlType = Control
32 |
--------------------------------------------------------------------------------
/components/ranking/patch/PatchList.tsx:
--------------------------------------------------------------------------------
1 | import { GalgameCard } from '~/components/galgame/Card'
2 |
3 | interface Props {
4 | patches: GalgameCard[]
5 | }
6 |
7 | export const PatchList = ({ patches }: Props) => {
8 | return (
9 |
10 | {patches.map((patch) => (
11 |
12 | ))}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/components/ranking/user/UserList.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { UserCard } from './UserCard'
4 | import type { RankingUser } from '~/types/api/ranking'
5 |
6 | interface Props {
7 | users: RankingUser[]
8 | sortBy?: string
9 | }
10 |
11 | export const UserList = ({
12 | users,
13 | sortBy = 'moemoepoint_increment'
14 | }: Props) => {
15 | return (
16 |
17 | {users.map((user, index) => (
18 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/components/ranking/user/UserStatsItem.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Tooltip } from '@nextui-org/tooltip'
4 | import { Badge } from '@nextui-org/badge'
5 | import { formatNumber } from '~/utils/formatNumber'
6 |
7 | export const UserStatsItem = ({
8 | icon,
9 | value,
10 | increment,
11 | label
12 | }: {
13 | icon: React.ReactNode
14 | value: number
15 | increment: number
16 | label: string
17 | }) => {
18 | const incrementLabel = `${increment > 0 ? '+' : ''}${formatNumber(increment)}`
19 | return (
20 |
27 |
28 |
29 | {icon}
30 | {value}
31 |
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/components/resource/_sort.d.ts:
--------------------------------------------------------------------------------
1 | export type SortOption = 'update_time' | 'created' | 'download' | 'like'
2 | export type SortDirection = 'asc' | 'desc'
3 |
--------------------------------------------------------------------------------
/components/settings/Nav.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/components/settings/Nav.tsx
--------------------------------------------------------------------------------
/components/settings/user/Avatar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Card, CardBody, CardFooter } from '@nextui-org/card'
4 | import { AvatarCrop } from './AvatarCrop'
5 |
6 | export const UserAvatar = () => {
7 | return (
8 |
9 |
10 |
11 |
头像
12 |
这是您的头像设置
13 |
您可以点击头像以上传图片文件
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 头像不是必须, 但是我们强烈推荐设置头像
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/components/settings/user/Container.tsx:
--------------------------------------------------------------------------------
1 | import { KunHeader } from '~/components/kun/Header'
2 | import { UserAvatar } from './Avatar'
3 | import { Username } from './Username'
4 | import { Bio } from './Bio'
5 | import { Email } from './Email'
6 | import { Password } from './Password'
7 | import { MessageSettings } from './MessageSettings'
8 | import { Reset } from './Reset'
9 |
10 | export const UserSettings = () => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/components/tag/TagHeader.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Button } from '@nextui-org/button'
4 | import { useDisclosure } from '@nextui-org/modal'
5 | import { Plus } from 'lucide-react'
6 | import { CreateTagModal } from '~/components/tag/CreateTagModal'
7 | import { KunHeader } from '../kun/Header'
8 | import type { Tag as TagType } from '~/types/api/tag'
9 |
10 | interface Props {
11 | setNewTag: (tag: TagType) => void
12 | }
13 |
14 | export const TagHeader = ({ setNewTag }: Props) => {
15 | const { isOpen, onOpen, onClose } = useDisclosure()
16 |
17 | return (
18 | <>
19 | }>
24 | 创建标签
25 |
26 | }
27 | />
28 |
29 | {
33 | setNewTag(newTag)
34 | onClose()
35 | }}
36 | />
37 | >
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/components/tag/TagList.tsx:
--------------------------------------------------------------------------------
1 | import { KunMasonryGrid } from '~/components/kun/MasonryGrid'
2 | import { TagCard } from './Card'
3 | import { KunNull } from '~/components/kun/Null'
4 | import { KunLoading } from '~/components/kun/Loading'
5 | import type { Tag as TagType } from '~/types/api/tag'
6 |
7 | interface TagListProps {
8 | tags: TagType[]
9 | loading: boolean
10 | searching: boolean
11 | }
12 |
13 | export const TagList = ({ tags, loading, searching }: TagListProps) => {
14 | if (loading) {
15 | return
16 | }
17 |
18 | if (!searching && tags.length === 0) {
19 | return
20 | }
21 |
22 | return (
23 |
24 | {tags.map((tag) => (
25 |
26 | ))}
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/components/user/contribute/Card.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardBody } from '@nextui-org/card'
2 | import { formatDate } from '~/utils/time'
3 | import Link from 'next/link'
4 | import type { UserContribute } from '~/types/api/user'
5 |
6 | interface Props {
7 | contribute: UserContribute
8 | }
9 |
10 | export const UserContributeCard = ({ contribute }: Props) => {
11 | return (
12 |
19 |
20 |
21 | {contribute.patchName}
22 |
23 |
24 | 贡献于{' '}
25 | {formatDate(contribute.created, {
26 | isPrecise: true,
27 | isShowYear: true
28 | })}
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/config/config.d.ts:
--------------------------------------------------------------------------------
1 | export interface KunSiteDomain {
2 | main: string
3 | imageBed: string
4 | storage: string
5 | kungal: string
6 | telegram_group: string
7 | cluster: string
8 | }
9 |
10 | export interface KunSiteAuthor {
11 | name: string
12 | url: string
13 | }
14 |
15 | export interface KunSiteOpenGraph {
16 | title: string
17 | description: string
18 | image: string
19 | url: string
20 | }
21 |
22 | export interface KunSiteCreator {
23 | name: string
24 | mention: string
25 | url: string
26 | }
27 |
28 | export interface KunSiteImage {
29 | url: string
30 | width: number
31 | height: number
32 | alt: string
33 | }
34 |
35 | export interface KunSiteConfig {
36 | title: string
37 | titleShort: string
38 | template: string
39 | description: string
40 | keywords: string[]
41 | canonical: string
42 | author: KunSiteAuthor[]
43 | creator: KunSiteCreator
44 | publisher: KunSiteCreator
45 | domain: KunSiteDomain
46 | og: KunSiteOpenGraph
47 | images: KunSiteImage[]
48 | }
49 |
--------------------------------------------------------------------------------
/config/friend.ts:
--------------------------------------------------------------------------------
1 | import friendsData from './friend.json'
2 |
3 | interface KunFriend {
4 | name: string
5 | avatar: string
6 | label: string
7 | link: string
8 | }
9 |
10 | export const kunFriends: KunFriend[] = friendsData.friends
11 |
--------------------------------------------------------------------------------
/config/redis.ts:
--------------------------------------------------------------------------------
1 | export const ADMIN_DELETE_EMAIL_CACHE_KEY = 'admin:delete:user:email'
2 | export const ADMIN_DELETE_IP_CACHE_KEY = 'admin:delete:user:ip'
3 |
4 | export const KUN_PATCH_DISABLE_REGISTER_KEY = 'admin:setting:register'
5 |
6 | export const KUN_PATCH_ENABLE_COMMENT_VERIFY_KEY =
7 | 'admin:setting:comment:verify'
8 |
9 | export const KUN_PATCH_ENABLE_ONLY_CREATOR_CREATE_KEY =
10 | 'admin:setting:creator:create'
11 |
--------------------------------------------------------------------------------
/config/search.ts:
--------------------------------------------------------------------------------
1 | export const KUN_PATCH_MAX_HISTORY_ITEMS = 10
2 |
--------------------------------------------------------------------------------
/config/upload.ts:
--------------------------------------------------------------------------------
1 | export const UPLOAD_IMAGE_COMPRESS_QUALITY = 60
2 | export const UPLOAD_IMAGE_MAX_SIZE = 1.007
3 |
4 | export const CHUNK_SIZE = 20 * 1024 * 1024
5 | export const UPLOAD_DIR = 'uploads/resource'
6 |
7 | export const MAX_SMALL_FILE_SIZE = 200 * 1024 * 1024
8 | export const MAX_LARGE_FILE_SIZE = 1 * 1024 * 1024 * 1024
9 |
10 | export const USER_DAILY_UPLOAD_LIMIT = 100 * 1024 * 1024
11 | export const CREATOR_DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024 * 1024
12 |
--------------------------------------------------------------------------------
/config/user.ts:
--------------------------------------------------------------------------------
1 | export const KUN_PATCH_USER_DAILY_UPLOAD_IMAGE_LIMIT = 200
2 |
--------------------------------------------------------------------------------
/constants/about.ts:
--------------------------------------------------------------------------------
1 | export const aboutDirectoryLabelMap: Record = {
2 | about: '关于我们',
3 | dev: '开发文档',
4 | galgame: 'Galgame',
5 | kun: '关于鲲',
6 | notice: '公告'
7 | }
8 |
--------------------------------------------------------------------------------
/constants/admin.ts:
--------------------------------------------------------------------------------
1 | import type { OverviewData } from '~/types/api/admin'
2 |
3 | export const APPLICANT_STATUS_MAP: Record = {
4 | 0: '待处理',
5 | 1: '已处理',
6 | 2: '已同意',
7 | 3: '已拒绝'
8 | }
9 |
10 | export const ADMIN_LOG_TYPE_MAP: Record = {
11 | create: '创建',
12 | delete: '删除',
13 | approve: '同意',
14 | decline: '拒绝',
15 | update: '更改'
16 | }
17 |
18 | export const ADMIN_STATS_SUM_MAP: Record = {
19 | userCount: '用户总数',
20 | galgameCount: 'Galgame 总数',
21 | patchResourceCount: 'Galgame 补丁总数',
22 | patchCommentCount: '评论总数'
23 | }
24 |
25 | export const ADMIN_STATS_MAP: Record = {
26 | newUser: '新注册用户',
27 | newActiveUser: '新活跃用户',
28 | newGalgame: '新发布 Galgame',
29 | newPatchResource: '新发布补丁',
30 | newComment: '新发布评论'
31 | }
32 |
--------------------------------------------------------------------------------
/constants/api/select.ts:
--------------------------------------------------------------------------------
1 | export const GalgameCardSelectField = {
2 | id: true,
3 | name: true,
4 | banner: true,
5 | view: true,
6 | download: true,
7 | type: true,
8 | language: true,
9 | platform: true,
10 | content_limit: true,
11 | created: true,
12 | _count: {
13 | select: {
14 | favorite_by: true,
15 | contribute_by: true,
16 | resource: true,
17 | comment: true
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/constants/captcha.ts:
--------------------------------------------------------------------------------
1 | export const kunCaptchaErrorMessageMap: Record = {
2 | 1: '杂鱼 ~ 连白毛老婆都分辨不出来了吗',
3 | 2: '杂鱼 ~ 怎么又选错了!',
4 | 3: '杂鱼 ~ 八嘎 ~ 杂鱼 ~ 臭杂鱼 ~',
5 | 4: '臭杂鱼 ~ 做不对验证的臭杂鱼 ~',
6 | 5: '八嘎八嘎八嘎八嘎八嘎八嘎八嘎八嘎',
7 | 6: '杂鱼 ~ 你不会是机器人吧'
8 | }
9 |
--------------------------------------------------------------------------------
/constants/company.ts:
--------------------------------------------------------------------------------
1 | export const LOGO_KEY = 'kun-patch-company-logo'
2 |
--------------------------------------------------------------------------------
/constants/galgame.ts:
--------------------------------------------------------------------------------
1 | export const GALGAME_AGE_LIMIT_MAP: Record = {
2 | sfw: 'SFW',
3 | nsfw: 'NSFW'
4 | }
5 |
6 | export const GALGAME_AGE_LIMIT_DETAIL: Record = {
7 | sfw: '本文章内容安全, 无 R18 等内容, 适合在公共场所浏览',
8 | nsfw: '本文章可能包含 R18 等内容, 不适合在公共场所浏览'
9 | }
10 |
11 | export const GALGAME_SORT_FIELD_LABEL_MAP: Record = {
12 | resource_update_time: '补丁更新时间',
13 | created: '游戏创建时间',
14 | view: '浏览量',
15 | download: '下载量'
16 | }
17 |
18 | const currentYear = new Date().getFullYear()
19 | export const GALGAME_SORT_YEARS = [
20 | 'all',
21 | 'future',
22 | 'unknown',
23 | ...Array.from({ length: currentYear - 1979 }, (_, i) =>
24 | String(currentYear - i)
25 | )
26 | ]
27 |
28 | export const GALGAME_SORT_YEARS_MAP: Record = {
29 | all: '全部年份',
30 | future: '未发售',
31 | unknown: '未知年份'
32 | }
33 |
34 | export const GALGAME_SORT_MONTHS = [
35 | 'all',
36 | '01',
37 | '02',
38 | '03',
39 | '04',
40 | '05',
41 | '06',
42 | '07',
43 | '08',
44 | '09',
45 | '10',
46 | '11',
47 | '12'
48 | ]
49 |
--------------------------------------------------------------------------------
/constants/history.ts:
--------------------------------------------------------------------------------
1 | export const HISTORY_ACTION_TYPE = [
2 | 'create',
3 | 'update',
4 | 'delete',
5 | 'merge',
6 | 'decline'
7 | ] as const
8 |
9 | export const HISTORY_ACTION_TYPE_MAP: Record = {
10 | create: '创建了',
11 | update: '更新了',
12 | delete: '删除了',
13 | merge: '合并了',
14 | decline: '拒绝了'
15 | }
16 |
17 | export const HISTORY_TYPE = [
18 | 'galgame',
19 | 'introduction',
20 | 'tag',
21 | 'pr',
22 | 'banner'
23 | ] as const
24 |
25 | export const HISTORY_TYPE_MAP: Record = {
26 | galgame: 'Galgame',
27 | tag: 'Galgame 标签',
28 | pr: '更新请求',
29 | banner: '预览图'
30 | }
31 |
--------------------------------------------------------------------------------
/constants/home-typed-js.ts:
--------------------------------------------------------------------------------
1 | export const kunTypedStrings = [
2 | '开源的 Galgame 补丁资源下载站',
3 | '免费的 Galgame 补丁资源下载站',
4 | '零门槛的 Galgame 补丁资源下载站',
5 | '纯手写的 Galgame 补丁资源下载站',
6 | '最先进的 Galgame 补丁资源下载站'
7 | ]
8 |
--------------------------------------------------------------------------------
/constants/message.ts:
--------------------------------------------------------------------------------
1 | export const MESSAGE_TYPE = [
2 | 'apply',
3 | 'pm',
4 | 'likeResource',
5 | 'likeComment',
6 | 'favorite',
7 | 'comment',
8 | 'follow',
9 | 'pr',
10 | 'mention',
11 | 'patchResourceCreate',
12 | 'patchResourceUpdate',
13 | 'system',
14 | ''
15 | ] as const
16 |
17 | export const MESSAGE_TYPE_MAP: Record = {
18 | apply: '申请',
19 | pm: '私聊',
20 | likeResource: '点赞资源',
21 | likeComment: '点赞评论',
22 | favorite: '收藏',
23 | comment: '评论',
24 | follow: '关注',
25 | pr: '更新请求',
26 | mention: '提到了您',
27 | patchResourceCreate: '创建新补丁',
28 | patchResourceUpdate: '更新补丁',
29 | system: '系统'
30 | }
31 |
--------------------------------------------------------------------------------
/constants/routes/matcher.ts:
--------------------------------------------------------------------------------
1 | export const isPatchPath = (pathname: string): boolean => {
2 | return /^\/patch\/\d+/.test(pathname)
3 | }
4 |
5 | export const isTagPath = (pathname: string): boolean => {
6 | return /^\/tag\/\d+/.test(pathname)
7 | }
8 |
9 | export const isCompanyPath = (pathname: string): boolean => {
10 | return /^\/company\/\d+/.test(pathname)
11 | }
12 |
13 | export const isUserPath = (pathname: string): boolean => {
14 | return /^\/user\/\d+/.test(pathname)
15 | }
16 |
--------------------------------------------------------------------------------
/constants/top-bar.ts:
--------------------------------------------------------------------------------
1 | export interface KunNavItem {
2 | name: string
3 | href: string
4 | }
5 |
6 | export const kunNavItem: KunNavItem[] = [
7 | {
8 | name: '下载',
9 | href: '/galgame'
10 | },
11 | {
12 | name: '发布',
13 | href: '/edit/create'
14 | },
15 | {
16 | name: '标签',
17 | href: '/tag'
18 | },
19 | {
20 | name: '会社',
21 | href: '/company'
22 | },
23 | {
24 | name: '新作',
25 | href: '/release'
26 | },
27 | {
28 | name: '排行',
29 | href: '/ranking/user'
30 | },
31 | {
32 | name: '关于',
33 | href: '/about'
34 | }
35 | ]
36 |
37 | export const kunMobileNavItem: KunNavItem[] = [
38 | ...kunNavItem,
39 | {
40 | name: '补丁评论列表',
41 | href: '/comment'
42 | },
43 | {
44 | name: '补丁资源列表',
45 | href: '/resource'
46 | },
47 | {
48 | name: '管理系统',
49 | href: '/admin'
50 | },
51 | {
52 | name: '联系我们',
53 | href: '/about/notice/feedback'
54 | }
55 | ]
56 |
--------------------------------------------------------------------------------
/constants/user.ts:
--------------------------------------------------------------------------------
1 | export const USER_ROLE_MAP: Record = {
2 | 1: '用户',
3 | 2: '创作者',
4 | 3: '管理员',
5 | 4: '超级管理员'
6 | }
7 |
8 | export const USER_STATUS_MAP: Record = {
9 | 0: '正常',
10 | 1: '限制(正在开发中)',
11 | 2: '封禁'
12 | }
13 |
14 | export const USER_STATUS_COLOR_MAP: Record<
15 | number,
16 | 'success' | 'warning' | 'danger'
17 | > = {
18 | 0: 'success',
19 | 1: 'warning',
20 | 2: 'danger'
21 | }
22 |
--------------------------------------------------------------------------------
/ecosystem.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | apps: [
5 | {
6 | name: 'kun-visual-novel-patch',
7 | port: 2333,
8 | cwd: path.join(__dirname),
9 | instances: 1,
10 | autorestart: true,
11 | watch: false,
12 | max_memory_restart: '1G',
13 | script: './.next/standalone/server.js'
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/hooks/useMounted.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export const useMounted = () => {
4 | const [mounted, setMounted] = useState(false)
5 |
6 | useEffect(() => {
7 | setMounted(true)
8 | }, [])
9 |
10 | return mounted
11 | }
12 |
--------------------------------------------------------------------------------
/hooks/useResizeObserver.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useCallback, useEffect, useState } from 'react'
4 |
5 | interface Size {
6 | width: number | undefined
7 | height: number | undefined
8 | }
9 |
10 | export const useResizeObserver = (ref: React.RefObject) => {
11 | const [size, setSize] = useState({
12 | width: undefined,
13 | height: undefined
14 | })
15 |
16 | const handleResize = useCallback((entries: ResizeObserverEntry[]) => {
17 | const entry = entries[0]
18 |
19 | if (entry) {
20 | const { width, height } = entry.contentRect
21 | setSize({ width, height })
22 | }
23 | }, [])
24 |
25 | useEffect(() => {
26 | if (!ref.current) {
27 | return
28 | }
29 |
30 | const observer = new ResizeObserver(handleResize)
31 | observer.observe(ref.current)
32 |
33 | return () => {
34 | observer.disconnect()
35 | }
36 | }, [ref, handleResize])
37 |
38 | return size
39 | }
40 |
--------------------------------------------------------------------------------
/hooks/useWindowSize.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState, useEffect } from 'react'
4 |
5 | interface WindowSize {
6 | width: number
7 | height: number
8 | }
9 |
10 | export const useWindowSize = (): WindowSize => {
11 | const [windowSize, setWindowSize] = useState({
12 | width: 0,
13 | height: 0
14 | })
15 |
16 | useEffect(() => {
17 | function handleResize() {
18 | setWindowSize({
19 | width: window.innerWidth,
20 | height: window.innerHeight
21 | })
22 | }
23 |
24 | window.addEventListener('resize', handleResize)
25 |
26 | handleResize()
27 |
28 | return () => window.removeEventListener('resize', handleResize)
29 | }, [])
30 |
31 | return windowSize
32 | }
33 |
--------------------------------------------------------------------------------
/instrumentation.ts:
--------------------------------------------------------------------------------
1 | export const register = async () => {
2 | if (process.env.NEXT_RUNTIME === 'nodejs') {
3 | const { setKUNGalgameTask } = await import('~/server/cron')
4 | setKUNGalgameTask()
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lib/mdx/CustomMDX.tsx:
--------------------------------------------------------------------------------
1 | import { MDXRemote, MDXRemoteProps } from 'next-mdx-remote/rsc'
2 | import { KunLink } from './element/KunLink'
3 | import { KunTable } from './element/KunTable'
4 | import { KunCode } from './element/KunCode'
5 | import { createKunHeading } from './element/kunHeading'
6 |
7 | const components = {
8 | h1: createKunHeading(1),
9 | h2: createKunHeading(2),
10 | h3: createKunHeading(3),
11 | h4: createKunHeading(4),
12 | h5: createKunHeading(5),
13 | h6: createKunHeading(6),
14 | a: KunLink,
15 | code: KunCode,
16 | Table: KunTable
17 | }
18 |
19 | export const CustomMDX = (props: MDXRemoteProps) => {
20 | return (
21 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/lib/mdx/element/KunCode.tsx:
--------------------------------------------------------------------------------
1 | import { highlight } from 'sugar-high'
2 | import React, { FC } from 'react'
3 |
4 | interface CodeProps extends React.HTMLAttributes {
5 | children: string
6 | }
7 |
8 | export const KunCode: FC = ({ children, ...props }) => {
9 | const codeHTML = highlight(children)
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/lib/mdx/element/KunLink.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import React, { FC } from 'react'
3 |
4 | interface CustomLinkProps
5 | extends React.AnchorHTMLAttributes {
6 | href: string
7 | }
8 |
9 | export const KunLink: FC = ({ href, children, ...props }) => {
10 | if (href.startsWith('/')) {
11 | return (
12 |
13 | {children}
14 |
15 | )
16 | }
17 |
18 | if (href.startsWith('#')) {
19 | return (
20 |
21 | {children}
22 |
23 | )
24 | }
25 |
26 | return (
27 |
28 | {children}
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/lib/mdx/element/KunTable.tsx:
--------------------------------------------------------------------------------
1 | interface TableProps {
2 | data: {
3 | headers: string[]
4 | rows: string[][]
5 | }
6 | }
7 |
8 | export const KunTable = ({ data }: TableProps) => {
9 | const headers = data.headers.map((header, index) => (
10 | {header} |
11 | ))
12 | const rows = data.rows.map((row, index) => (
13 |
14 | {row.map((cell, cellIndex) => (
15 | {cell} |
16 | ))}
17 |
18 | ))
19 |
20 | return (
21 |
22 |
23 | {headers}
24 |
25 | {rows}
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/lib/mdx/element/kunHeading.ts:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 |
3 | const slugify = (str: string): string => {
4 | return str
5 | .toString()
6 | .toLowerCase()
7 | .trim()
8 | .replace(/\s+/g, '-')
9 | .replace(/&/g, '-and-')
10 | .replace(/[^\p{L}\p{N}]+/gu, '')
11 | .replace(/--+/g, '-')
12 | .replace(/^-+|-+$/g, '')
13 | }
14 |
15 | export const createKunHeading = (level: number) => {
16 | const Heading = ({ children }: { children: ReactNode }) => {
17 | const slug = slugify(children?.toString() || '')
18 | return React.createElement(
19 | `h${level}`,
20 | { id: slug },
21 | [
22 | React.createElement('a', {
23 | href: `#${slug}`,
24 | key: `kun-link-${slug}`,
25 | className: 'kun-anchor',
26 | 'aria-label': slug
27 | })
28 | ],
29 | children
30 | )
31 | }
32 |
33 | Heading.displayName = `KunHeading${level}`
34 |
35 | return Heading
36 | }
37 |
--------------------------------------------------------------------------------
/lib/mdx/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface KunPostMetadata {
2 | title: string
3 | banner: string
4 | date: string
5 | description: string
6 | textCount: number
7 | slug: string
8 | path: string
9 | }
10 |
11 | export interface KunTreeNode {
12 | name: string
13 | label: string
14 | path: string
15 | children?: KunTreeNode[]
16 | type: 'file' | 'directory'
17 | }
18 |
19 | export interface KunFrontmatter {
20 | title: string
21 | banner: string
22 | description: string
23 | date: string
24 | authorUid: number
25 | authorName: string
26 | authorAvatar: string
27 | authorHomepage: string
28 | }
29 |
30 | export interface KunBlog {
31 | slug: string
32 | content: string
33 | frontmatter: KunFrontmatter
34 | }
35 |
--------------------------------------------------------------------------------
/lib/redis.ts:
--------------------------------------------------------------------------------
1 | import Redis from 'ioredis'
2 |
3 | const KUN_PATCH_REDIS_PREFIX = 'kun:patch'
4 |
5 | export const redis = new Redis({
6 | port: parseInt(process.env.REDIS_PORT!),
7 | host: process.env.REDIS_HOST
8 | })
9 |
10 | export const setKv = async (key: string, value: string, time?: number) => {
11 | const keyString = `${KUN_PATCH_REDIS_PREFIX}:${key}`
12 | if (time) {
13 | await redis.setex(keyString, time, value)
14 | } else {
15 | await redis.set(keyString, value)
16 | }
17 | }
18 |
19 | export const getKv = async (key: string) => {
20 | const keyString = `${KUN_PATCH_REDIS_PREFIX}:${key}`
21 | const value = await redis.get(keyString)
22 | return value
23 | }
24 |
25 | export const delKv = async (key: string) => {
26 | const keyString = `${KUN_PATCH_REDIS_PREFIX}:${key}`
27 | await redis.del(keyString)
28 | }
29 |
--------------------------------------------------------------------------------
/lib/s3/client.ts:
--------------------------------------------------------------------------------
1 | import { S3Client } from '@aws-sdk/client-s3'
2 |
3 | export const s3 = new S3Client({
4 | endpoint: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_ENDPOINT!,
5 | region: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_REGION!,
6 | credentials: {
7 | accessKeyId: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_ACCESS_KEY_ID!,
8 | secretAccessKey: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_SECRET_ACCESS_KEY!
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/lib/s3/deleteFileFromS3.ts:
--------------------------------------------------------------------------------
1 | import { s3 } from './client'
2 | import { DeleteObjectCommand } from '@aws-sdk/client-s3'
3 |
4 | export const deleteFileFromS3 = async (key: string) => {
5 | const deleteCommand = new DeleteObjectCommand({
6 | Bucket: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_BUCKET_NAME!,
7 | Key: key
8 | })
9 | await s3.send(deleteCommand)
10 | }
11 |
--------------------------------------------------------------------------------
/lib/s3/uploadImageToS3.ts:
--------------------------------------------------------------------------------
1 | import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
2 |
3 | // Image will put to a different storage provider
4 | export const s3 = new S3Client({
5 | endpoint: process.env.KUN_VISUAL_NOVEL_IMAGE_BED_ENDPOINT!,
6 | region: 'auto',
7 | credentials: {
8 | accessKeyId: process.env.KUN_VISUAL_NOVEL_IMAGE_BED_ACCESS_KEY!,
9 | secretAccessKey: process.env.KUN_VISUAL_NOVEL_IMAGE_BED_SECRET_KEY!
10 | }
11 | })
12 |
13 | export const uploadImageToS3 = async (key: string, fileBuffer: Buffer) => {
14 | const uploadCommand = new PutObjectCommand({
15 | Bucket: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_BUCKET_NAME!,
16 | Key: key,
17 | Body: fileBuffer,
18 | ContentType: 'application/octet-stream'
19 | })
20 | await s3.send(uploadCommand)
21 | }
22 |
--------------------------------------------------------------------------------
/lib/s3/uploadSmallFileToS3.ts:
--------------------------------------------------------------------------------
1 | import { s3 } from './client'
2 | import { readFile } from 'fs/promises'
3 | import { PutObjectCommand } from '@aws-sdk/client-s3'
4 |
5 | export const uploadSmallFileToS3 = async (key: string, filePath: string) => {
6 | try {
7 | const fileBuffer = await readFile(filePath)
8 | const uploadCommand = new PutObjectCommand({
9 | Bucket: process.env.KUN_VISUAL_NOVEL_S3_STORAGE_BUCKET_NAME!,
10 | Key: key,
11 | Body: fileBuffer,
12 | ContentType: 'application/octet-stream'
13 | })
14 | await s3.send(uploadCommand)
15 | } catch (error) {
16 | return '上传文件错误, uploadSmallFileToS3 function ERROR'
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/middleware/_verifyHeaderCookie.ts:
--------------------------------------------------------------------------------
1 | import { parseCookies } from '~/utils/cookies'
2 | import { verifyKunToken } from '~/app/api/utils/jwt'
3 | import type { NextRequest } from 'next/server'
4 |
5 | export const verifyHeaderCookie = async (req: NextRequest) => {
6 | const token = parseCookies(req.headers.get('cookie') ?? '')[
7 | 'kun-galgame-patch-moe-token'
8 | ]
9 | const payload = await verifyKunToken(token ?? '')
10 |
11 | return payload
12 | }
13 |
--------------------------------------------------------------------------------
/middleware/middleware.ts:
--------------------------------------------------------------------------------
1 | // import { NextResponse } from 'next/server'
2 | // import type { NextRequest } from 'next/server'
3 |
4 | // export async function middleware(request: NextRequest) {
5 | // const { kunAuthMiddleware } = await import('./auth')
6 | // kunAuthMiddleware(request)
7 | // return NextResponse.next()
8 | // }
9 |
--------------------------------------------------------------------------------
/migration/migrateAlias.mjs:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 | import { readFile } from 'fs/promises'
3 | import path from 'path'
4 | import { fileURLToPath } from 'url'
5 |
6 | const __filename = fileURLToPath(import.meta.url)
7 | const __dirname = path.dirname(__filename)
8 |
9 | const prisma = new PrismaClient()
10 |
11 | async function main() {
12 | try {
13 | const filePath = `${__dirname}/patches.json`
14 | const data = await readFile(filePath, 'utf-8')
15 | const patches = JSON.parse(data)
16 |
17 | console.log(`开始迁移 ${patches.length} 条 alias 数据...`)
18 |
19 | for (const { id, alias } of patches) {
20 | await prisma.patch_alias.create({
21 | data: {
22 | name: alias,
23 | patch: {
24 | connect: { id: id }
25 | }
26 | }
27 | })
28 | }
29 |
30 | console.log('数据迁移完成!')
31 | } catch (error) {
32 | console.error('数据迁移失败:', error)
33 | } finally {
34 | await prisma.$disconnect()
35 | }
36 | }
37 |
38 | main()
39 |
--------------------------------------------------------------------------------
/migration/updatePatchResourceUpdateTime.mjs:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const prisma = new PrismaClient()
4 |
5 | const updatePatchResourceUpdateTime = async () => {
6 | try {
7 | const patches = await prisma.patch.findMany({
8 | select: {
9 | id: true,
10 | created: true
11 | }
12 | })
13 |
14 | for (const patch of patches) {
15 | await prisma.patch.update({
16 | where: { id: patch.id },
17 | data: {
18 | resource_update_time: patch.created
19 | }
20 | })
21 |
22 | console.log(`Updated patch with id: ${patch.id}`)
23 | }
24 |
25 | console.log('Successfully updated all patch records.')
26 | } catch (error) {
27 | console.error('Error updating patch records:', error)
28 | } finally {
29 | await prisma.$disconnect()
30 | }
31 | }
32 |
33 | updatePatchResourceUpdateTime()
34 |
--------------------------------------------------------------------------------
/migration/updateReleased.mjs:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const prisma = new PrismaClient()
4 |
5 | async function updateReleasedField() {
6 | try {
7 | const updatedRecords = await prisma.patch.updateMany({
8 | where: {
9 | released: ''
10 | },
11 | data: {
12 | released: 'unknown'
13 | }
14 | })
15 |
16 | console.log(`Updated ${updatedRecords.count} records.`)
17 | } catch (error) {
18 | console.error('Error updating records: ', error)
19 | } finally {
20 | await prisma.$disconnect()
21 | }
22 | }
23 |
24 | updateReleasedField()
25 |
--------------------------------------------------------------------------------
/migration/updateResourceUpdateTime.mjs:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const prisma = new PrismaClient()
4 |
5 | const updateResourceUpdateTime = async () => {
6 | try {
7 | const resources = await prisma.patch_resource.findMany({
8 | select: {
9 | id: true,
10 | created: true
11 | }
12 | })
13 |
14 | for (const resource of resources) {
15 | await prisma.patch_resource.update({
16 | where: { id: resource.id },
17 | data: {
18 | update_time: resource.created
19 | }
20 | })
21 |
22 | console.log(`Updated resource with id: ${resource.id}`)
23 | }
24 |
25 | console.log('Successfully updated all resource records.')
26 | } catch (error) {
27 | console.error('Error updating resource records:', error)
28 | } finally {
29 | await prisma.$disconnect()
30 | }
31 | }
32 |
33 | updateResourceUpdateTime()
34 |
--------------------------------------------------------------------------------
/migration/userDailyUploadSize.mjs:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const prisma = new PrismaClient()
4 |
5 | async function migrateDailyUploadSize() {
6 | try {
7 | console.log('Starting migration...')
8 |
9 | // Step 1: 更新所有用户的数据,将 MB 转换为 Byte(避免精度丢失)
10 | await prisma.$executeRaw`UPDATE "user" SET "daily_upload_size" = "daily_upload_size" * 1024 * 1024;`
11 |
12 | console.log('Data conversion completed.')
13 |
14 | // Step 2: 修改数据库字段类型
15 | await prisma.$executeRaw`ALTER TABLE "user" ALTER COLUMN "daily_upload_size" SET DATA TYPE INT;`
16 |
17 | console.log('Column type updated to INT.')
18 | } catch (error) {
19 | console.error('Migration failed:', error)
20 | } finally {
21 | await prisma.$disconnect()
22 | }
23 | }
24 |
25 | // 执行迁移
26 | migrateDailyUploadSize()
27 |
--------------------------------------------------------------------------------
/motion/bell.ts:
--------------------------------------------------------------------------------
1 | export const dotVariants = {
2 | initial: { scale: 0, opacity: 0 },
3 | animate: { scale: 1, opacity: 1 },
4 | exit: { scale: 0, opacity: 0 }
5 | }
6 |
7 | export const bellShakeVariants = {
8 | initial: { rotate: 0 },
9 | animate: {
10 | rotate: [0, -10, 10, -10, 10, 0],
11 | transition: {
12 | duration: 0.5,
13 | repeat: Infinity,
14 | repeatDelay: 2
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/motion/card.ts:
--------------------------------------------------------------------------------
1 | export const cardContainer = {
2 | hidden: { opacity: 0 },
3 | show: {
4 | opacity: 1,
5 | transition: {
6 | staggerChildren: 0.1
7 | }
8 | }
9 | }
10 |
11 | export const cardItem = {
12 | hidden: { opacity: 0, y: 20 },
13 | show: { opacity: 1, y: 0 }
14 | }
15 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'tailwindcss/nesting': {},
4 | tailwindcss: {},
5 | autoprefixer: {}
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/prisma/index.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const globalForPrisma = global as unknown as { prisma: PrismaClient }
4 |
5 | export const prisma = globalForPrisma.prisma ?? new PrismaClient()
6 |
7 | if (process.env.NODE_ENV !== 'production') {
8 | globalForPrisma.prisma = prisma
9 | }
10 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/apple-touch-icon.avif
--------------------------------------------------------------------------------
/public/edit/1.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/edit/1.avif
--------------------------------------------------------------------------------
/public/edit/2.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/edit/2.avif
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/favicon.webp
--------------------------------------------------------------------------------
/public/kungalgame.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/kungalgame.avif
--------------------------------------------------------------------------------
/public/placeholder.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/placeholder.webp
--------------------------------------------------------------------------------
/public/posts/dev/documentation/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/dev/documentation/banner.avif
--------------------------------------------------------------------------------
/public/posts/galgame/resource/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/galgame/resource/banner.avif
--------------------------------------------------------------------------------
/public/posts/kun/moe/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/kun/moe/banner.avif
--------------------------------------------------------------------------------
/public/posts/kun/moe/kun.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/kun/moe/kun.webp
--------------------------------------------------------------------------------
/public/posts/kun/ren/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/kun/ren/banner.avif
--------------------------------------------------------------------------------
/public/posts/kun/ren/ren1.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/kun/ren/ren1.avif
--------------------------------------------------------------------------------
/public/posts/kun/ren/ren2.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/kun/ren/ren2.avif
--------------------------------------------------------------------------------
/public/posts/notice/about/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/about/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/cfmsc/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/cfmsc/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/creator/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/creator/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/feedback/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/feedback/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/galgame-tutorial/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/galgame-tutorial/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/moemoepoint/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/moemoepoint/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/nsfw/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/nsfw/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/open-source/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/open-source/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/paradigm/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/paradigm/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image1.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image1.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image2.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image2.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image3.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image3.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image4.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image4.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image5.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image5.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image6.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image6.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image7.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image7.avif
--------------------------------------------------------------------------------
/public/posts/notice/patch-tutorial/image8.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/patch-tutorial/image8.avif
--------------------------------------------------------------------------------
/public/posts/notice/privacy/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/privacy/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/rule/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/rule/banner.avif
--------------------------------------------------------------------------------
/public/posts/notice/update/banner.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/posts/notice/update/banner.avif
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Disallow: /message
4 |
--------------------------------------------------------------------------------
/public/sooner/あーちゃん.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/sooner/あーちゃん.webp
--------------------------------------------------------------------------------
/public/sooner/こじかひわ.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/sooner/こじかひわ.webp
--------------------------------------------------------------------------------
/public/sooner/琥珀.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/sooner/琥珀.webp
--------------------------------------------------------------------------------
/public/sooner/雪々.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/public/sooner/雪々.webp
--------------------------------------------------------------------------------
/scripts/deployBuild.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import { config } from 'dotenv'
3 | import { envSchema } from '../validations/dotenv-check'
4 | import * as fs from 'fs'
5 | import * as path from 'path'
6 |
7 | const envPath = path.resolve(__dirname, '..', '.env')
8 | if (!fs.existsSync(envPath)) {
9 | console.error('.env file not found in the project root.')
10 | process.exit(1)
11 | }
12 |
13 | config({ path: envPath })
14 |
15 | try {
16 | envSchema.safeParse(process.env)
17 |
18 | console.log('Environment variables are valid.')
19 | console.log('Executing the commands...')
20 |
21 | execSync(
22 | 'git pull && pnpm prisma:push && pnpm build && pnpm stop && pnpm start',
23 | { stdio: 'inherit' }
24 | )
25 | } catch (error) {
26 | console.error('Invalid environment variables', error)
27 | process.exit(1)
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/deployInstall.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 |
3 | const runCommand = (command: string) => {
4 | try {
5 | console.log(`Running command: ${command}`)
6 | execSync(command, { stdio: 'inherit' })
7 | } catch (error) {
8 | console.error(`Error running command: ${command}`, error)
9 | process.exit(1)
10 | }
11 | }
12 |
13 | runCommand('pnpm install')
14 |
15 | runCommand('pnpx prisma:push')
16 |
--------------------------------------------------------------------------------
/server/cron.ts:
--------------------------------------------------------------------------------
1 | import { resetDailyTask } from './tasks/resetDailyTask'
2 | import { setCleanupTask } from './tasks/setCleanupTask'
3 |
4 | export const setKUNGalgameTask = () => {
5 | resetDailyTask
6 | setCleanupTask
7 | }
8 |
--------------------------------------------------------------------------------
/server/image/auth/other/22.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/22.webp
--------------------------------------------------------------------------------
/server/image/auth/other/24.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/24.webp
--------------------------------------------------------------------------------
/server/image/auth/other/29.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/29.webp
--------------------------------------------------------------------------------
/server/image/auth/other/37.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/37.webp
--------------------------------------------------------------------------------
/server/image/auth/other/38.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/38.webp
--------------------------------------------------------------------------------
/server/image/auth/other/46.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/46.webp
--------------------------------------------------------------------------------
/server/image/auth/other/50.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/50.webp
--------------------------------------------------------------------------------
/server/image/auth/other/54.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/54.webp
--------------------------------------------------------------------------------
/server/image/auth/other/59.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/59.webp
--------------------------------------------------------------------------------
/server/image/auth/other/64.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/64.webp
--------------------------------------------------------------------------------
/server/image/auth/other/65.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/65.webp
--------------------------------------------------------------------------------
/server/image/auth/other/70.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/70.webp
--------------------------------------------------------------------------------
/server/image/auth/other/71.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/other/71.webp
--------------------------------------------------------------------------------
/server/image/auth/white/14.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/14.webp
--------------------------------------------------------------------------------
/server/image/auth/white/16.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/16.webp
--------------------------------------------------------------------------------
/server/image/auth/white/26.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/26.webp
--------------------------------------------------------------------------------
/server/image/auth/white/51.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/51.webp
--------------------------------------------------------------------------------
/server/image/auth/white/59.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/59.webp
--------------------------------------------------------------------------------
/server/image/auth/white/6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/6.webp
--------------------------------------------------------------------------------
/server/image/auth/white/7.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/7.webp
--------------------------------------------------------------------------------
/server/image/auth/white/76.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/76.webp
--------------------------------------------------------------------------------
/server/image/auth/white/8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/8.webp
--------------------------------------------------------------------------------
/server/image/auth/white/9.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUN1007/kun-galgame-patch-next/5edcf363d5120951016d376e5213b8b3affd542d/server/image/auth/white/9.webp
--------------------------------------------------------------------------------
/server/tasks/resetDailyTask.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from '~/prisma'
2 | import cron from 'node-cron'
3 |
4 | export const resetDailyTask = cron.schedule('0 0 * * *', async () => {
5 | await prisma.user.updateMany({
6 | data: {
7 | daily_image_count: 0,
8 | daily_check_in: 0,
9 | daily_upload_size: 0
10 | }
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/store/milkdownStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 |
3 | export interface MilkdownData {
4 | refreshContentStatus: boolean
5 | }
6 |
7 | interface StoreState {
8 | data: MilkdownData
9 | refreshMilkdownContent: () => void
10 | }
11 |
12 | const initialState: MilkdownData = {
13 | refreshContentStatus: false
14 | }
15 |
16 | export const useKunMilkdownStore = create()((set, get) => ({
17 | data: initialState,
18 | refreshMilkdownContent: () => {
19 | const currentStatus = get().data.refreshContentStatus
20 | set({ data: { refreshContentStatus: !currentStatus } })
21 | }
22 | }))
23 |
--------------------------------------------------------------------------------
/store/rewriteStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 |
3 | export interface RewritePatchData {
4 | id: number
5 | vndbId: string
6 | name: string
7 | introduction: string
8 | alias: string[]
9 | contentLimit: string
10 | released: string
11 | }
12 |
13 | interface StoreState {
14 | data: RewritePatchData
15 | getData: () => RewritePatchData
16 | setData: (data: RewritePatchData) => void
17 | resetData: () => void
18 | }
19 |
20 | const initialState: RewritePatchData = {
21 | id: 0,
22 | vndbId: '',
23 | name: '',
24 | introduction: '',
25 | alias: [],
26 | contentLimit: 'sfw',
27 | released: ''
28 | }
29 |
30 | export const useRewritePatchStore = create()((set, get) => ({
31 | data: initialState,
32 | getData: () => get().data,
33 | setData: (data: RewritePatchData) => set({ data }),
34 | resetData: () => set({ data: initialState })
35 | }))
36 |
--------------------------------------------------------------------------------
/store/searchStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist, createJSONStorage } from 'zustand/middleware'
3 |
4 | export interface CreateSearchData {
5 | searchHistory: string[]
6 | searchInIntroduction: boolean
7 | searchInAlias: boolean
8 | searchInTag: boolean
9 | }
10 |
11 | const initialState: CreateSearchData = {
12 | searchHistory: [],
13 | searchInIntroduction: false,
14 | searchInAlias: true,
15 | searchInTag: false
16 | }
17 |
18 | interface SearchStoreState {
19 | data: CreateSearchData
20 | getData: () => CreateSearchData
21 | setData: (data: CreateSearchData) => void
22 | resetData: () => void
23 | }
24 |
25 | export const useSearchStore = create()(
26 | persist(
27 | (set, get) => ({
28 | data: initialState,
29 | getData: () => get().data,
30 | setData: (data: CreateSearchData) => set({ data }),
31 | resetData: () => set({ data: initialState })
32 | }),
33 | {
34 | name: 'kun-patch-search-store',
35 | storage: createJSONStorage(() => localStorage)
36 | }
37 | )
38 | )
39 |
--------------------------------------------------------------------------------
/store/settingStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist, createJSONStorage } from 'zustand/middleware'
3 | import { cookieStorage } from './_cookie'
4 |
5 | export interface KunSettingData {
6 | kunNsfwEnable: string
7 | }
8 |
9 | interface StoreState {
10 | data: KunSettingData
11 | getData: () => KunSettingData
12 | setData: (data: KunSettingData) => void
13 | resetData: () => void
14 | }
15 |
16 | const initialState: KunSettingData = {
17 | kunNsfwEnable: 'sfw'
18 | }
19 |
20 | export const useSettingStore = create()(
21 | persist(
22 | (set, get) => ({
23 | data: initialState,
24 | getData: () => get().data,
25 | setData: (data: KunSettingData) => set({ data }),
26 | resetData: () => set({ data: initialState })
27 | }),
28 | {
29 | name: 'kun-patch-setting-store',
30 | storage: createJSONStorage(() => cookieStorage)
31 | }
32 | )
33 | )
34 |
--------------------------------------------------------------------------------
/styles/blog.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --sh-class: hsl(var(--nextui-primary-500));
3 | --sh-identifier: hsl(var(--nextui-default-800));
4 | --sh-sign: hsl(var(--nextui-default-500));
5 | --sh-property: hsl(var(--nextui-primary-700));
6 | --sh-entity: hsl(var(--nextui-success-600));
7 | --sh-jsxliterals: hsl(var(--nextui-secondary-600));
8 | --sh-string: hsl(var(--nextui-success-500));
9 | --sh-keyword: hsl(var(--nextui-warning-600));
10 | --sh-comment: hsl(var(--nextui-default-400));
11 | }
12 |
--------------------------------------------------------------------------------
/styles/index.scss:
--------------------------------------------------------------------------------
1 | @use './tailwind.scss';
2 | @use './blog.scss';
3 | @use './prose.scss';
4 |
5 | [data-overlay-container='true'] {
6 | height: 100%;
7 | width: 100%;
8 | }
9 |
10 | /** TODO: **/
11 | * {
12 | font-family: '微软雅黑';
13 | }
14 |
--------------------------------------------------------------------------------
/styles/tailwind.scss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | // tailwind.config.js
2 | import { nextui } from '@nextui-org/react'
3 | import typography from '@tailwindcss/typography'
4 |
5 | /** @type {import('tailwindcss').Config} */
6 | const config = {
7 | content: [
8 | './app/**/*.{js,ts,jsx,tsx,mdx}',
9 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
10 | './components/**/*.{js,ts,jsx,tsx,mdx}',
11 | './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
12 | ],
13 | theme: {
14 | extend: {}
15 | },
16 | darkMode: 'class',
17 | future: {
18 | hoverOnlyWhenSupported: true
19 | },
20 | plugins: [nextui(), typography]
21 | }
22 |
23 | export default config
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "~/*": ["./*"]
20 | },
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/types/api/comment.d.ts:
--------------------------------------------------------------------------------
1 | export interface PatchComment {
2 | id: number
3 | user: KunUser
4 | content: string
5 | patchName: string
6 | patchId: number
7 | like: number
8 | created: Date | string
9 | }
10 |
--------------------------------------------------------------------------------
/types/api/company.d.ts:
--------------------------------------------------------------------------------
1 | export interface Company {
2 | id: number
3 | name: string
4 | logo: string
5 | count: number
6 | alias: string[]
7 | }
8 |
9 | export interface CompanyDetail extends Company {
10 | introduction: string
11 | primary_language: string[]
12 | official_website: string[]
13 | parent_brand: string[]
14 | created: string | Date
15 | user: KunUser
16 | }
17 |
--------------------------------------------------------------------------------
/types/api/galgame.d.ts:
--------------------------------------------------------------------------------
1 | interface GalgameCard {
2 | id: number
3 | name: string
4 | banner: string
5 | view: number
6 | download: number
7 | type: string[]
8 | language: string[]
9 | platform: string[]
10 | content_limit: string
11 | created: Date | string
12 | _count: {
13 | favorite_by: number
14 | contribute_by: number
15 | resource: number
16 | comment: number
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/types/api/home.d.ts:
--------------------------------------------------------------------------------
1 | import type { PatchComment } from './comment'
2 | import type { PatchResource } from './resource'
3 |
4 | export interface HomeCarousel {
5 | id: number
6 | galgameTitle: string
7 | description: string
8 | type: string[]
9 | language: string[]
10 | platform: string[]
11 | }
12 |
13 | export type HomeResource = PatchResource
14 | export type HomeComment = PatchComment
15 |
--------------------------------------------------------------------------------
/types/api/message.d.ts:
--------------------------------------------------------------------------------
1 | import { MESSAGE_TYPE } from '~/constants/message'
2 |
3 | export interface Message {
4 | id: number
5 | type: string
6 | content: string
7 | status: number
8 | link: string
9 | created: string | Date
10 | sender: KunUser | null
11 | }
12 |
13 | export interface CreateMessageType {
14 | type: (typeof MESSAGE_TYPE)[number]
15 | content: string
16 | link: string
17 | sender_id?: number
18 | recipient_id?: number
19 | }
20 |
--------------------------------------------------------------------------------
/types/api/ranking.d.ts:
--------------------------------------------------------------------------------
1 | export type RankingUser = {
2 | id: number
3 | name: string
4 | avatar: string
5 | moemoepoint: number
6 | patchCount: number
7 | resourceCount: number
8 | commentCount: number
9 | }
10 |
--------------------------------------------------------------------------------
/types/api/release.d.ts:
--------------------------------------------------------------------------------
1 | export interface GalgameReleaseCard {
2 | patchId: number
3 | name: string
4 | banner: string
5 | released: string
6 | resourceCount: number
7 | }
8 |
--------------------------------------------------------------------------------
/types/api/resource.d.ts:
--------------------------------------------------------------------------------
1 | export interface PatchResource {
2 | id: number
3 | storage: string
4 | name: string
5 | modelName: string
6 | size: string
7 | type: string[]
8 | language: string[]
9 | platform: string[]
10 | note: string
11 | likeCount: number
12 | download: number
13 | patchId: number
14 | patchName: string
15 | created: string
16 | user: KunUser & {
17 | patchCount: number
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/types/api/tag.d.ts:
--------------------------------------------------------------------------------
1 | export interface Tag {
2 | id: number
3 | name: string
4 | count: number
5 | alias: string[]
6 | }
7 |
8 | export interface TagDetail extends Tag {
9 | introduction: string
10 | created: string | Date
11 | user: KunUser
12 | }
13 |
--------------------------------------------------------------------------------
/types/api/upload.d.ts:
--------------------------------------------------------------------------------
1 | import { SUPPORTED_RESOURCE_LINK } from '~/constants/resource'
2 |
3 | export interface UploadFileResponse {
4 | filetype: (typeof SUPPORTED_RESOURCE_LINK)[number]
5 | fileHash: string
6 | fileSize: string
7 | }
8 |
9 | export interface KunChunkMetadata {
10 | chunkIndex: number
11 | totalChunks: number
12 | fileId: string
13 | fileName: string
14 | fileSize: number
15 | mimeType: string
16 | filepath: string
17 | fileHash: string
18 | }
19 |
--------------------------------------------------------------------------------
/types/response.d.ts:
--------------------------------------------------------------------------------
1 | type KunResponse = string | T
2 |
--------------------------------------------------------------------------------
/types/user.d.ts:
--------------------------------------------------------------------------------
1 | interface KunUser {
2 | id: number
3 | name: string
4 | avatar: string
5 | }
6 |
--------------------------------------------------------------------------------
/utils/actions/getNSFWHeader.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { cookies } from 'next/headers'
4 |
5 | export const getNSFWHeader = async () => {
6 | const cookieStore = await cookies()
7 | const token = cookieStore.get(
8 | 'kun-patch-setting-store|state|data|kunNsfwEnable'
9 | )?.value
10 |
11 | if (!token) {
12 | return { content_limit: 'sfw' }
13 | }
14 |
15 | if (token === 'all') {
16 | return {}
17 | } else {
18 | return { content_limit: token }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/utils/actions/safeParseSchema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import type { ZodSchema } from 'zod'
3 |
4 | export const safeParseSchema = (
5 | schema: T,
6 | object: Record
7 | ): z.infer | string => {
8 | const result = schema.safeParse(object)
9 | if (!result.success) {
10 | return result.error.message
11 | }
12 | return result.data
13 | }
14 |
--------------------------------------------------------------------------------
/utils/actions/verifyHeaderCookie.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { cookies } from 'next/headers'
4 | import { verifyKunToken } from '~/app/api/utils/jwt'
5 |
6 | export const verifyHeaderCookie = async () => {
7 | const cookieStore = await cookies()
8 | const token = cookieStore.get('kun-galgame-patch-moe-token')
9 | const payload = await verifyKunToken(token?.value ?? '')
10 |
11 | return payload
12 | }
13 |
--------------------------------------------------------------------------------
/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export const cn = (...inputs: ClassValue[]) => {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/utils/cookies.ts:
--------------------------------------------------------------------------------
1 | export const parseCookies = (cookieString: string) => {
2 | const cookiesKv: { [key: string]: string } = {}
3 | cookieString &&
4 | cookieString.split(';').forEach((cookie) => {
5 | const parts: string[] = cookie.split('=')
6 | if (parts.length) {
7 | cookiesKv[parts.shift()!.trim()] = decodeURI(parts.join('='))
8 | }
9 | })
10 | return cookiesKv
11 | }
12 |
--------------------------------------------------------------------------------
/utils/dataURItoBlob.ts:
--------------------------------------------------------------------------------
1 | export const dataURItoBlob = (dataURI: string) => {
2 | const byteString = atob(dataURI.split(',')[1])
3 | const ab = new ArrayBuffer(byteString.length)
4 | const ia = new Uint8Array(ab)
5 | for (let i = 0; i < byteString.length; i++) {
6 | ia[i] = byteString.charCodeAt(i)
7 | }
8 | return new Blob([ab], { type: 'image/webp' })
9 | }
10 |
--------------------------------------------------------------------------------
/utils/formatNumber.ts:
--------------------------------------------------------------------------------
1 | export const formatNumber = (num: number) => {
2 | if (num >= 1_000_000) {
3 | return (num / 1_000_000).toFixed(1) + 'M'
4 | } else if (num >= 10_000) {
5 | return (num / 10_000).toFixed(1) + 'w'
6 | } else if (num >= 1_000) {
7 | return (num / 1_000).toFixed(1) + 'k'
8 | } else {
9 | return num.toString()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/utils/kunCopy.ts:
--------------------------------------------------------------------------------
1 | import toast from 'react-hot-toast'
2 |
3 | const decodeIfEncoded = (text: string) => {
4 | try {
5 | const decoded = decodeURIComponent(text)
6 | return decoded !== text ? decoded : text
7 | } catch (e) {
8 | return text
9 | }
10 | }
11 |
12 | export const kunCopy = (originText: string) => {
13 | const text = decodeIfEncoded(originText)
14 |
15 | navigator.clipboard
16 | .writeText(text)
17 | .then(() =>
18 | toast.success(`${text} 复制成功`, {
19 | style: {
20 | whiteSpace: 'pre-wrap',
21 | wordBreak: 'break-all'
22 | }
23 | })
24 | )
25 | .catch(() => toast.error('复制失败! 请更换更现代的浏览器!'))
26 | }
27 |
--------------------------------------------------------------------------------
/utils/lz.ts:
--------------------------------------------------------------------------------
1 | declare module 'lz-string' {
2 | export function compressToBase64(input: string): string
3 | export function decompressFromBase64(input: string): string
4 |
5 | export function compressToUTF16(input: string): string
6 | export function decompressFromUTF16(compressed: string): string
7 |
8 | export function compressToUint8Array(uncompressed: string): Uint8Array
9 | export function decompressFromUint8Array(compressed: Uint8Array): string
10 |
11 | export function compressToEncodedURIComponent(input: string): string
12 | export function decompressFromEncodedURIComponent(compressed: string): string
13 |
14 | export function compress(input: string): string
15 | export function decompress(compressed: string): string
16 | }
17 |
18 | import { compressToBase64, decompressFromBase64 } from 'lz-string'
19 |
20 | export const encode = compressToBase64
21 |
22 | export const decode = decompressFromBase64
23 |
--------------------------------------------------------------------------------
/utils/markdownToText.tsx:
--------------------------------------------------------------------------------
1 | export const markdownToText = (markdown: string) => {
2 | return markdown
3 | .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
4 | .replace(/!\[([^\]]*)\]\([^\)]+\)/g, '$1')
5 | .replace(/(\*\*|__)(.*?)\1/g, '$2')
6 | .replace(/(\*|_)(.*?)\1/g, '$2')
7 | .replace(/^\s*(#{1,6})\s+(.*)/gm, '$2')
8 | .replace(/```[\s\S]*?```|`([^`]*)`/g, '$1')
9 | .replace(/^(-{3,}|\*{3,})$/gm, '')
10 | .replace(/^\s*([-*+]|\d+\.)\s+/gm, '')
11 | .replace(/\n{2,}/g, '\n')
12 | .trim()
13 | }
14 |
--------------------------------------------------------------------------------
/utils/random.ts:
--------------------------------------------------------------------------------
1 | export const randomNum = (lowerValue: number, upperValue: number) => {
2 | return Math.floor(Math.random() * (upperValue - lowerValue + 1) + lowerValue)
3 | }
4 |
--------------------------------------------------------------------------------
/utils/sanitizeFileName.ts:
--------------------------------------------------------------------------------
1 | export const sanitizeFileName = (fileName: string) => {
2 | const match = fileName.match(/^(.*?)(\.[^.]+)?$/)
3 | if (!match) {
4 | return fileName
5 | }
6 |
7 | const baseName = match[1]
8 | const extension = match[2] || ''
9 |
10 | const sanitizedBaseName = baseName.replace(/[^\p{L}\p{N}_-]/gu, '')
11 |
12 | return `${sanitizedBaseName.slice(0, 100)}${extension}`
13 | }
14 |
--------------------------------------------------------------------------------
/utils/time.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 |
3 | export const hourDiff = (upvoteTime: number, hours: number) => {
4 | if (upvoteTime === 0 || upvoteTime === undefined) {
5 | return false
6 | }
7 |
8 | const currentTime = dayjs()
9 |
10 | const time = dayjs(upvoteTime)
11 |
12 | return currentTime.diff(time, 'hour') <= hours
13 | }
14 |
15 | export const formatDate = (
16 | time: Date | number | string,
17 | config?: { isShowYear?: boolean; isPrecise?: boolean }
18 | ): string => {
19 | let formatString = 'MM-DD'
20 |
21 | if (config?.isShowYear) {
22 | formatString = 'YYYY-MM-DD'
23 | }
24 |
25 | if (config?.isPrecise) {
26 | formatString = `${formatString} - HH:mm`
27 | }
28 |
29 | return dayjs(time).format(formatString)
30 | }
31 |
--------------------------------------------------------------------------------
/validations/comment.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const commentSchema = z.object({
4 | sortField: z.union([z.literal('created'), z.literal('like')]),
5 | sortOrder: z.union([z.literal('asc'), z.literal('desc')]),
6 | page: z.coerce.number().min(1).max(9999999),
7 | limit: z.coerce.number().min(1).max(50)
8 | })
9 |
--------------------------------------------------------------------------------
/validations/galgame.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const galgameSchema = z.object({
4 | selectedType: z.string().min(1).max(107),
5 | sortField: z.union([
6 | z.literal('resource_update_time'),
7 | z.literal('created'),
8 | z.literal('view'),
9 | z.literal('download')
10 | ]),
11 | sortOrder: z.union([z.literal('asc'), z.literal('desc')]),
12 | page: z.coerce.number().min(1).max(9999999),
13 | limit: z.coerce.number().min(1).max(24),
14 | yearString: z.string().max(1007),
15 | monthString: z.string().max(1007)
16 | })
17 |
--------------------------------------------------------------------------------
/validations/message.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { MESSAGE_TYPE } from '~/constants/message'
3 |
4 | export const createMessageSchema = z.object({
5 | type: z.enum(MESSAGE_TYPE),
6 | content: z
7 | .string()
8 | .url('请输入有效的链接格式')
9 | .max(1007, { message: '单个链接的长度最大 1007 个字符' }),
10 | recipientId: z.coerce.number().min(1).max(9999999),
11 | link: z.string().max(1007)
12 | })
13 |
14 | export const getMessageSchema = z.object({
15 | type: z.enum(MESSAGE_TYPE).optional(),
16 | page: z.coerce.number().min(1).max(9999999),
17 | limit: z.coerce.number().min(1).max(30)
18 | })
19 |
--------------------------------------------------------------------------------
/validations/release.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const getReleaseSchema = z.object({
4 | year: z.coerce.number().min(1).max(5000),
5 | month: z.coerce.number().min(1).max(12)
6 | })
7 |
--------------------------------------------------------------------------------
/validations/resource.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const resourceSchema = z.object({
4 | sortField: z.union([
5 | z.literal('update_time'),
6 | z.literal('created'),
7 | z.literal('download'),
8 | z.literal('like')
9 | ]),
10 | sortOrder: z.union([z.literal('asc'), z.literal('desc')]),
11 | page: z.coerce.number().min(1).max(9999999),
12 | limit: z.coerce.number().min(1).max(50)
13 | })
14 |
--------------------------------------------------------------------------------
/validations/search.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const searchSchema = z.object({
4 | query: z
5 | .array(
6 | z
7 | .string()
8 | .trim()
9 | .min(1)
10 | .max(107, { message: '单个搜索关键词最大长度为 107' })
11 | )
12 | .min(1)
13 | .max(10, { message: '您最多使用 10 组关键词' }),
14 | page: z.coerce.number().min(1).max(9999999),
15 | limit: z.coerce.number().min(1).max(24),
16 | searchOption: z.object({
17 | searchInIntroduction: z.boolean().default(false),
18 | searchInAlias: z.boolean().default(false),
19 | searchInTag: z.boolean().default(false)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/validations/walkthrough.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const createWalkthroughSchema = z.object({
4 | patchId: z.coerce.number().min(1).max(9999999),
5 | name: z
6 | .string()
7 | .min(3, { message: '攻略标题最少三个字' })
8 | .max(233, { message: '攻略标题最多 233 字' }),
9 | content: z
10 | .string()
11 | .min(3, { message: '攻略内容最少三个字' })
12 | .max(100007, { message: '攻略内容最多 100007 字' })
13 | })
14 |
15 | export const updateWalkthroughSchema = z.object({
16 | walkthroughId: z.coerce.number().min(1).max(9999999),
17 | name: z
18 | .string()
19 | .min(3, { message: '攻略标题最少三个字' })
20 | .max(233, { message: '攻略标题最多 233 字' }),
21 | content: z
22 | .string()
23 | .min(3, { message: '攻略内容最少三个字' })
24 | .max(100007, { message: '攻略内容最多 100007 字' })
25 | })
26 |
--------------------------------------------------------------------------------