diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..18c4237
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+.env
+Dockerfile
+docker-compose.yml
+.dockerignore
+node_modules
+npm-debug.log
+README.md
+.next
+.git
\ No newline at end of file
diff --git a/.env b/.env
index 368b148..28afa16 100644
--- a/.env
+++ b/.env
@@ -8,4 +8,4 @@ MINIO_ACCESS_KEY=spider
MINIO_SECRET_KEY=spiderman!
MINIO_ENDPOINT=http://docker.pve:9001/api/v1
MINIO_ENABLED=1
-DATABASE_URL=postgres://postgres:postgres@database.pve/spider
\ No newline at end of file
+DATABASE_URL=mysql://vault:vault@database.pve/spider
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index b7ef4f8..1fae422 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,22 +4,17 @@ FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
-COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
+COPY package.json yarn.lock ./
RUN yarn config set registry https://nexus.yoshino-s.xyz/repository/npm/
-RUN \
- if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
- elif [ -f package-lock.json ]; then npm ci; \
- elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
- else echo "Lockfile not found." && exit 1; \
- fi
+RUN yarn --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
-RUN yarn build
-
+RUN yarn prisma generate && yarn build
+
FROM base AS runner
WORKDIR /app
diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx
new file mode 100644
index 0000000..fe20215
--- /dev/null
+++ b/components/Header/Header.tsx
@@ -0,0 +1,94 @@
+import { ActionIcon, createStyles, Group, Header, rem, TextInput } from "@mantine/core";
+import { useForm } from "@mantine/form";
+import { IconSearch } from "@tabler/icons";
+import { useRouter } from "next/router";
+
+const useStyles = createStyles((theme) => ({
+ header: {
+ paddingLeft: theme.spacing.md,
+ paddingRight: theme.spacing.md,
+ },
+
+ inner: {
+ height: rem(56),
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ },
+
+ links: {
+ [theme.fn.smallerThan("md")]: {
+ display: "none",
+ },
+ },
+
+ search: {
+ [theme.fn.smallerThan("xs")]: {
+ display: "none",
+ },
+ },
+
+ link: {
+ display: "block",
+ lineHeight: 1,
+ padding: `${rem(8)} ${rem(12)}`,
+ borderRadius: theme.radius.sm,
+ textDecoration: "none",
+ color: theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.colors.gray[7],
+ fontSize: theme.fontSizes.sm,
+ fontWeight: 500,
+
+ "&:hover": {
+ backgroundColor: theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
+ },
+ },
+}));
+
+interface HeaderSearchProps {
+ links: { link: string; label: string }[];
+}
+
+export function HeaderSearch({ links }: HeaderSearchProps) {
+ const { classes } = useStyles();
+ const router = useRouter();
+
+ const form = useForm({
+ initialValues: {
+ search: "",
+ },
+ });
+
+ const items = links.map((link) => (
+
+ {link.label}
+
+ ));
+
+ function submit({ search }: { search: string }) {
+ router.push({
+ pathname: "/search/[word]",
+ query: { word: search },
+ });
+ }
+
+ return (
+
+ );
+}
diff --git a/components/ParagraphGrid/ParagraphGrid.tsx b/components/ParagraphGrid/ParagraphGrid.tsx
index fa2957b..5b6bd5e 100644
--- a/components/ParagraphGrid/ParagraphGrid.tsx
+++ b/components/ParagraphGrid/ParagraphGrid.tsx
@@ -52,7 +52,7 @@ export default function ParagraphGrid({
{paragraphs.map((paragraph) => {
return (
-
+
(props.colorScheme);
@@ -25,6 +27,18 @@ export default function App(props: AppProps & { colorScheme: ColorScheme }) {
+
diff --git a/pages/index.tsx b/pages/index.tsx
index 64dcfd8..279625c 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,7 +1,5 @@
-import { Button } from "@mantine/core";
import { Paragraph } from "@prisma/client";
import { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
-import { useRouter } from "next/router";
import ParagraphGrid from "../components/ParagraphGrid/ParagraphGrid";
import { prismaClient } from "../lib/db";
@@ -14,15 +12,7 @@ interface ListProps {
}
export default function HomePage(props: ListProps) {
- const router = useRouter();
-
- return (
- router.push("/statistic")}>Statistic}
- {...props}
- />
- );
+ return ;
}
export async function getServerSideProps(
diff --git a/pages/paragraph/[id].tsx b/pages/paragraph/[id].tsx
index aa8625f..3ac27e1 100644
--- a/pages/paragraph/[id].tsx
+++ b/pages/paragraph/[id].tsx
@@ -48,7 +48,7 @@ export default function ParagraphPage({ paragraph }: { paragraph: Paragraph }) {
- {paragraph.tags.map((tag) => (
+ {paragraph.tags.split(",").map((tag) => (
diff --git a/pages/search/[word].tsx b/pages/search/[word].tsx
new file mode 100644
index 0000000..96abb90
--- /dev/null
+++ b/pages/search/[word].tsx
@@ -0,0 +1,70 @@
+import { Paragraph } from "@prisma/client";
+import { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
+
+import ParagraphGrid from "../../components/ParagraphGrid/ParagraphGrid";
+import { prismaClient } from "../../lib/db";
+
+interface ListProps {
+ paragraphs: Omit[];
+ skip: number;
+ take: number;
+ total: number;
+ word: string;
+}
+
+export default function TagPage(props: ListProps) {
+ return ;
+}
+
+export async function getServerSideProps(
+ ctx: GetServerSidePropsContext
+): Promise> {
+ const skip = Number(ctx.query.skip ?? 0);
+ const take = Number(ctx.query.take ?? 12);
+ const word = ctx.params?.word;
+
+ if (typeof word !== "string") {
+ return { notFound: true };
+ }
+
+ const condition = {
+ content: {
+ search: word,
+ },
+ tags: {
+ search: word,
+ },
+ author: {
+ search: word,
+ },
+ title: {
+ search: word,
+ },
+ };
+
+ const [total, paragraphs] = await Promise.all([
+ prismaClient.paragraph.count({
+ where: condition,
+ }),
+ await prismaClient.paragraph.findMany({
+ where: condition,
+ skip: Number(skip),
+ take: Number(take),
+ orderBy: {
+ time: "desc",
+ },
+ select: {
+ id: true,
+ title: true,
+ tags: true,
+ time: true,
+ author: true,
+ cover: true,
+ },
+ }),
+ ]);
+ paragraphs.forEach((paragraph) => {
+ paragraph.time = paragraph.time.getTime() as any;
+ });
+ return { props: { word, paragraphs, skip, take, total } };
+}
diff --git a/pages/tag copy/[name].tsx b/pages/tag copy/[name].tsx
new file mode 100644
index 0000000..a631d0e
--- /dev/null
+++ b/pages/tag copy/[name].tsx
@@ -0,0 +1,61 @@
+import { Paragraph } from "@prisma/client";
+import { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
+
+import ParagraphGrid from "../../components/ParagraphGrid/ParagraphGrid";
+import { prismaClient } from "../../lib/db";
+
+interface ListProps {
+ paragraphs: Omit[];
+ skip: number;
+ take: number;
+ total: number;
+ tag: string;
+}
+
+export default function TagPage(props: ListProps) {
+ return ;
+}
+
+export async function getServerSideProps(
+ ctx: GetServerSidePropsContext
+): Promise> {
+ const skip = Number(ctx.query.skip ?? 0);
+ const take = Number(ctx.query.take ?? 12);
+ const tag = ctx.params?.name;
+
+ if (!tag || typeof tag !== "string") {
+ return { notFound: true };
+ }
+
+ const condition = {
+ tags: {
+ contains: tag,
+ },
+ };
+
+ const [total, paragraphs] = await Promise.all([
+ prismaClient.paragraph.count({
+ where: condition,
+ }),
+ await prismaClient.paragraph.findMany({
+ where: condition,
+ skip: Number(skip),
+ take: Number(take),
+ orderBy: {
+ time: "desc",
+ },
+ select: {
+ id: true,
+ title: true,
+ tags: true,
+ time: true,
+ author: true,
+ cover: true,
+ },
+ }),
+ ]);
+ paragraphs.forEach((paragraph) => {
+ paragraph.time = paragraph.time.getTime() as any;
+ });
+ return { props: { tag, paragraphs, skip, take, total } };
+}
diff --git a/pages/tag/[name].tsx b/pages/tag/[name].tsx
index 85621ad..a631d0e 100644
--- a/pages/tag/[name].tsx
+++ b/pages/tag/[name].tsx
@@ -29,7 +29,7 @@ export async function getServerSideProps(
const condition = {
tags: {
- has: tag,
+ contains: tag,
},
};
diff --git a/prisma/migrations/20230314084213_dev/migration.sql b/prisma/migrations/20230314084213_dev/migration.sql
deleted file mode 100644
index 254086a..0000000
--- a/prisma/migrations/20230314084213_dev/migration.sql
+++ /dev/null
@@ -1,13 +0,0 @@
--- CreateTable
-CREATE TABLE "paragraph" (
- "id" TEXT NOT NULL,
- "author" TEXT NOT NULL,
- "content" TEXT NOT NULL,
- "cover" TEXT,
- "markdown" BOOLEAN,
- "tags" TEXT[],
- "time" DATE NOT NULL,
- "title" TEXT NOT NULL,
-
- CONSTRAINT "paragraph_pkey" PRIMARY KEY ("id")
-);
diff --git a/prisma/migrations/20230314090553_dev/migration.sql b/prisma/migrations/20230314090553_dev/migration.sql
deleted file mode 100644
index d51e0ae..0000000
--- a/prisma/migrations/20230314090553_dev/migration.sql
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- Warnings:
-
- - You are about to drop the `paragraph` table. If the table is not empty, all the data it contains will be lost.
-
-*/
--- DropTable
-DROP TABLE "paragraph";
-
--- CreateTable
-CREATE TABLE "Paragraph" (
- "id" TEXT NOT NULL,
- "author" TEXT NOT NULL,
- "content" TEXT NOT NULL,
- "cover" TEXT,
- "markdown" BOOLEAN,
- "tags" TEXT[],
- "time" DATE NOT NULL,
- "title" TEXT NOT NULL,
-
- CONSTRAINT "Paragraph_pkey" PRIMARY KEY ("id")
-);
diff --git a/prisma/migrations/20230316135037_dev/migration.sql b/prisma/migrations/20230316135037_dev/migration.sql
new file mode 100644
index 0000000..ec91521
--- /dev/null
+++ b/prisma/migrations/20230316135037_dev/migration.sql
@@ -0,0 +1,17 @@
+-- CreateTable
+CREATE TABLE `Paragraph` (
+ `id` VARCHAR(191) NOT NULL,
+ `author` VARCHAR(191) NOT NULL,
+ `content` VARCHAR(191) NOT NULL,
+ `cover` VARCHAR(191) NULL,
+ `markdown` BOOLEAN NULL,
+ `tags` VARCHAR(191) NOT NULL,
+ `time` DATE NOT NULL,
+ `title` VARCHAR(191) NOT NULL,
+
+ INDEX `Paragraph_author_idx`(`author`),
+ INDEX `Paragraph_time_idx`(`time`),
+ INDEX `Paragraph_title_idx`(`title`),
+ FULLTEXT INDEX `Paragraph_content_author_title_tags_idx`(`content`, `author`, `title`, `tags`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/prisma/migrations/20230316135533_dev/migration.sql b/prisma/migrations/20230316135533_dev/migration.sql
new file mode 100644
index 0000000..99ca840
--- /dev/null
+++ b/prisma/migrations/20230316135533_dev/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE `Paragraph` MODIFY `content` LONGTEXT NOT NULL;
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
index fbffa92..e5a788a 100644
--- a/prisma/migrations/migration_lock.toml
+++ b/prisma/migrations/migration_lock.toml
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
-provider = "postgresql"
\ No newline at end of file
+provider = "mysql"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 40df40a..967c771 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1,19 +1,25 @@
generator client {
- provider = "prisma-client-js"
+ provider = "prisma-client-js"
+ previewFeatures = ["fullTextSearch", "fullTextIndex"]
}
datasource db {
- provider = "postgres"
+ provider = "mysql"
url = env("DATABASE_URL")
}
model Paragraph {
id String @id
author String
- content String
+ content String @db.LongText
cover String?
markdown Boolean?
- tags String[]
+ tags String
time DateTime @db.Date
title String
+
+ @@index([author])
+ @@index([time])
+ @@index([title])
+ @@fulltext([content, author, title, tags])
}
diff --git a/yarn.lock b/yarn.lock
index ce46178..54baaa2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1745,6 +1745,14 @@
dependencies:
"@mantine/utils" "6.0.0"
+"@mantine/form@^6.0.2":
+ version "6.0.2"
+ resolved "https://nexus.yoshino-s.xyz/repository/npm/@mantine/form/-/form-6.0.2.tgz#560b0e2b9e5775ec564e87c2c4d8bcf46e43f805"
+ integrity sha512-M18h8NBhV3P1iq+A1xS1x4dFN+xREu9k6/NmY5ftwYf4UwhVgiscKQhJ28ZC+m+oAtMZmdBhsHM5LVnFquPQJA==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ klona "^2.0.5"
+
"@mantine/hooks@6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-6.0.0.tgz#08b67946e0b45f67181efa9e37df68f92a8ee6d1"
@@ -8678,6 +8686,11 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
+klona@^2.0.5:
+ version "2.0.6"
+ resolved "https://nexus.yoshino-s.xyz/repository/npm/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
+ integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
+
language-subtag-registry@~0.3.2:
version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"