diff --git a/.env b/.env
deleted file mode 100644
index dadb0b7..0000000
--- a/.env
+++ /dev/null
@@ -1,4 +0,0 @@
-# 数据库 url
-DATABASE_URL="postgres://postgres:666666@localhost:5432/postgres"
-# AUTH_SECRET npx auth secret
-AUTH_SECRET=your-secret-key
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..50eb5cb
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+# 数据库 url
+DATABASE_URL="postgres://postgres:postgres@localhost:5432/postgres"
+# AUTH_SECRET npx auth secret
+AUTH_SECRET=your-secret-key
+# 禁用 Vercel node.js 帮助程序
+NODEJS_HELPERS=0
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b37bdea..04cd403 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,4 @@ dist
/build
# local env files
-.env*.local
\ No newline at end of file
+.env
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 7980c9f..01471d6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -54,6 +54,7 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV AUTH_TRUST_HOST true
+ENV NODEJS_HELPERS 0
EXPOSE 3000
diff --git a/app/(default)/[...tag]/page.tsx b/app/(default)/[...tag]/page.tsx
index 190292d..17c3f31 100644
--- a/app/(default)/[...tag]/page.tsx
+++ b/app/(default)/[...tag]/page.tsx
@@ -1,5 +1,5 @@
import Masonry from '~/components/Masonry'
-import { fetchClientImagesListByTag, fetchClientImagesPageTotalByTag } from '~/server/lib/query'
+import { fetchClientImagesListByTag, fetchClientImagesPageTotalByTag } from '~/server/db/query'
import { ImageHandleProps } from '~/types'
export default function Page({ params }: { params: { tag: string } }) {
diff --git a/app/(default)/label/[...tag]/page.tsx b/app/(default)/label/[...tag]/page.tsx
index 4deda5f..e903b56 100644
--- a/app/(default)/label/[...tag]/page.tsx
+++ b/app/(default)/label/[...tag]/page.tsx
@@ -1,5 +1,5 @@
import Masonry from '~/components/Masonry'
-import { fetchClientImagesListByLabel, fetchClientImagesPageTotalByLabel } from '~/server/lib/query'
+import { fetchClientImagesListByLabel, fetchClientImagesPageTotalByLabel } from '~/server/db/query'
import { ImageHandleProps } from '~/types'
export default function Label({ params }: { params: { tag: string } }) {
diff --git a/app/(default)/page.tsx b/app/(default)/page.tsx
index 28fb9b6..b382952 100644
--- a/app/(default)/page.tsx
+++ b/app/(default)/page.tsx
@@ -1,5 +1,5 @@
import Masonry from '~/components/Masonry'
-import { fetchClientImagesListByTag, fetchClientImagesPageTotalByTag } from '~/server/lib/query'
+import { fetchClientImagesListByTag, fetchClientImagesPageTotalByTag } from '~/server/db/query'
import { ImageHandleProps } from '~/types'
export default async function Home() {
diff --git a/app/admin/about/page.tsx b/app/admin/about/page.tsx
index e0058ef..c7f4e38 100644
--- a/app/admin/about/page.tsx
+++ b/app/admin/about/page.tsx
@@ -33,7 +33,7 @@ export default function About() {
width={64}
height={64}
/>
- v1.0.0
+ v1.1.0
PicImpact 是一个摄影师专用的摄影作品展示网站,基于 Next.js 开发。
diff --git a/app/admin/copyright/page.tsx b/app/admin/copyright/page.tsx
index e745b02..2ab4714 100644
--- a/app/admin/copyright/page.tsx
+++ b/app/admin/copyright/page.tsx
@@ -1,4 +1,4 @@
-import { fetchCopyrightList } from '~/server/lib/query'
+import { fetchCopyrightList } from '~/server/db/query'
import { HandleProps } from '~/types'
import { Card, CardHeader } from '@nextui-org/react'
import React from 'react'
diff --git a/app/admin/list/page.tsx b/app/admin/list/page.tsx
index d274bef..e9ed1b5 100644
--- a/app/admin/list/page.tsx
+++ b/app/admin/list/page.tsx
@@ -1,7 +1,7 @@
import {
fetchServerImagesListByTag,
fetchServerImagesPageTotalByTag
-} from '~/server/lib/query'
+} from '~/server/db/query'
import { ImageServerHandleProps } from '~/types'
import ListProps from '~/components/admin/list/ListProps'
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
index 1b4f12e..1c099ab 100644
--- a/app/admin/page.tsx
+++ b/app/admin/page.tsx
@@ -1,4 +1,4 @@
-import { fetchImagesAnalysis } from '~/server/lib/query'
+import { fetchImagesAnalysis } from '~/server/db/query'
import CardList from '~/components/admin/dashboard/CardList'
import { DataProps } from '~/types'
diff --git a/app/admin/settings/authenticator/page.tsx b/app/admin/settings/authenticator/page.tsx
index e401522..a9a11d3 100644
--- a/app/admin/settings/authenticator/page.tsx
+++ b/app/admin/settings/authenticator/page.tsx
@@ -41,7 +41,7 @@ export default function Authenticator() {
async function getQRCode() {
try {
- const res = await fetch('/api/v1/get-seed-secret', {
+ const res = await fetch('/api/v1/auth/get-seed-secret', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -60,7 +60,7 @@ export default function Authenticator() {
async function saveAuthTemplateToken() {
try {
- const res = await fetch('/api/v1/auth-validate', {
+ const res = await fetch('/api/v1/auth/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -82,7 +82,7 @@ export default function Authenticator() {
async function removeAuth() {
try {
setDeleteLoading(true)
- const res = await fetch('/api/v1/remove-auth', {
+ const res = await fetch('/api/v1/auth/remove', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
diff --git a/app/admin/settings/password/page.tsx b/app/admin/settings/password/page.tsx
index dfd93a2..b98fa83 100644
--- a/app/admin/settings/password/page.tsx
+++ b/app/admin/settings/password/page.tsx
@@ -37,7 +37,7 @@ export default function PassWord() {
}
try {
setLoading(true)
- await fetch('/api/v1/update-password', {
+ await fetch('/api/v1/settings/update-password', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/app/admin/settings/preferences/page.tsx b/app/admin/settings/preferences/page.tsx
index 73f6ad0..ced9b56 100644
--- a/app/admin/settings/preferences/page.tsx
+++ b/app/admin/settings/preferences/page.tsx
@@ -1,7 +1,7 @@
'use client'
import { Button, Card, CardBody, Input } from '@nextui-org/react'
-import React, {useEffect, useState} from 'react'
+import React, { useEffect, useState } from 'react'
import useSWR from 'swr'
import { fetcher } from '~/utils/fetcher'
import { toast } from 'sonner'
@@ -10,12 +10,12 @@ export default function Preferences() {
const [title, setTitle] = useState('')
const [loading, setLoading] = useState(false)
- const { data, isValidating, isLoading } = useSWR('/api/v1/get-custom-title', fetcher)
+ const { data, isValidating, isLoading } = useSWR('/api/v1/settings/get-custom-title', fetcher)
async function updateTitle() {
try {
setLoading(true)
- await fetch('/api/v1/update-custom-title', {
+ await fetch('/api/v1/settings/update-custom-title', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/app/admin/tag/page.tsx b/app/admin/tag/page.tsx
index ac06398..e174001 100644
--- a/app/admin/tag/page.tsx
+++ b/app/admin/tag/page.tsx
@@ -1,4 +1,4 @@
-import { fetchTagsList } from '~/server/lib/query'
+import { fetchTagsList } from '~/server/db/query'
import TagList from '~/components/admin/tag/TagList'
import { Card, CardHeader } from '@nextui-org/react'
import RefreshButton from '~/components/RefreshButton'
diff --git a/app/api/[[...route]]/route.ts b/app/api/[[...route]]/route.ts
new file mode 100644
index 0000000..52dc9c0
--- /dev/null
+++ b/app/api/[[...route]]/route.ts
@@ -0,0 +1,22 @@
+import 'server-only'
+import { handle } from 'hono/vercel'
+import { Hono } from 'hono'
+import route from '~/hono'
+import open from '~/hono/open/open'
+
+const app = new Hono().basePath('/api')
+
+app.route('/v1', route)
+app.route('/open', open)
+app.notFound((c) => {
+ return c.text('not found', 404)
+})
+
+export const GET = handle(app)
+export const POST = handle(app)
+export const PUT = handle(app)
+export const DELETE = handle(app)
+export const dynamic = 'force-dynamic'
+export const runtime = 'nodejs'
+
+export default app
diff --git a/app/api/open/get-auth-status/route.ts b/app/api/open/get-auth-status/route.ts
deleted file mode 100644
index 8c76351..0000000
--- a/app/api/open/get-auth-status/route.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import 'server-only'
-import { queryAuthStatus } from '~/server/lib/query'
-
-export async function GET() {
- try {
- const data = await queryAuthStatus();
-
- return Response.json({
- code: 200,
- message: '获取双因素状态成功!',
- data: {
- auth_enable: data?.config_value
- }
- })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '获取双因素状态失败!' })
- }
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/open/get-image-blob/route.ts b/app/api/open/get-image-blob/route.ts
deleted file mode 100644
index f96c768..0000000
--- a/app/api/open/get-image-blob/route.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import 'server-only'
-import { NextRequest } from 'next/server'
-
-export async function GET(req: NextRequest) {
- const { searchParams } = new URL(req.url)
- const imageUrl = searchParams.get('imageUrl')
- // @ts-ignore
- const blob = await fetch(imageUrl).then(res => res.blob())
- return new Response(blob)
-}
-
-export const revalidate = 0
\ No newline at end of file
diff --git a/app/api/open/get-image-by-id/route.ts b/app/api/open/get-image-by-id/route.ts
deleted file mode 100644
index b789093..0000000
--- a/app/api/open/get-image-by-id/route.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'server-only'
-import { fetchImageByIdAndAuth } from '~/server/lib/query'
-import { NextRequest } from 'next/server'
-
-export async function GET(req: NextRequest) {
- const { searchParams } = new URL(req.url)
- const id = searchParams.get('id')
- const data = await fetchImageByIdAndAuth(Number(id));
- if (data && data?.length > 0) {
- return Response.json({ code: 200, message: '图片数据获取成功!', data: data })
- } else {
- return Response.json({ code: 500, message: '图片不存在或未公开展示!' })
- }
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/route.ts b/app/api/route.ts
deleted file mode 100644
index 18357e1..0000000
--- a/app/api/route.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import 'server-only'
-
-export async function GET() {
- return Response.json('hello')
-}
\ No newline at end of file
diff --git a/app/api/v1/alist-info/route.ts b/app/api/v1/alist-info/route.ts
deleted file mode 100644
index 4c8fad8..0000000
--- a/app/api/v1/alist-info/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchAListInfo } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchAListInfo();
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/auth-login-validate/route.ts b/app/api/v1/auth-login-validate/route.ts
deleted file mode 100644
index 7536632..0000000
--- a/app/api/v1/auth-login-validate/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import 'server-only'
-import { queryAuthSecret } from '~/server/lib/query'
-import { NextRequest } from 'next/server'
-import * as OTPAuth from 'otpauth'
-
-export async function POST(req: NextRequest) {
- const data = await req.json()
- try {
- const secret = await queryAuthSecret();
- let totp = new OTPAuth.TOTP({
- issuer: "PicImpact",
- label: "admin",
- algorithm: "SHA512",
- digits: 6,
- period: 30,
- // @ts-ignore
- secret: OTPAuth.Secret.fromBase32(secret?.config_value),
- });
- let delta = totp.validate({ token: data.token, window: 1 })
- if (delta === 0) {
- return Response.json({ code: 200, message: '双因素口令验证成功!' })
- }
- return Response.json({ code: 500, message: '双因素口令验证失败!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '双因素口令验证失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/auth-validate/route.ts b/app/api/v1/auth-validate/route.ts
deleted file mode 100644
index 8cc9179..0000000
--- a/app/api/v1/auth-validate/route.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import 'server-only'
-import { queryAuthTemplateSecret } from '~/server/lib/query'
-import { NextRequest } from 'next/server'
-import * as OTPAuth from 'otpauth'
-import { saveAuthSecret } from '~/server/lib/operate'
-
-export async function POST(req: NextRequest) {
- const data = await req.json()
- try {
- const secret = await queryAuthTemplateSecret();
- let totp = new OTPAuth.TOTP({
- issuer: "PicImpact",
- label: "admin",
- algorithm: "SHA512",
- digits: 6,
- period: 30,
- // @ts-ignore
- secret: OTPAuth.Secret.fromBase32(secret?.config_value),
- });
- let delta = totp.validate({ token: data.token, window: 1 })
- if (delta === 0) {
- // @ts-ignore
- await saveAuthSecret('true', secret?.config_value)
- return Response.json({ code: 200, message: '设置成功!' })
- }
- return Response.json({ code: 500, message: '设置失败!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '设置失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/copyright-add/route.ts b/app/api/v1/copyright-add/route.ts
deleted file mode 100644
index cbd3101..0000000
--- a/app/api/v1/copyright-add/route.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'server-only'
-import { insertCopyright } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function POST(req: NextRequest) {
- const copyright = await req.json()
- try {
- await insertCopyright(copyright);
- return Response.json({ code: 200, message: '新增成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '新增失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/copyright-delete/[id]/route.ts b/app/api/v1/copyright-delete/[id]/route.ts
deleted file mode 100644
index 40005bb..0000000
--- a/app/api/v1/copyright-delete/[id]/route.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'server-only'
-import { deleteCopyright } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function DELETE(
- req: NextRequest,
- { params }: { params: { id: number } },
-) {
- const data = await deleteCopyright(params.id);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/copyright-update/route.ts b/app/api/v1/copyright-update/route.ts
deleted file mode 100644
index f106564..0000000
--- a/app/api/v1/copyright-update/route.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'server-only'
-import { updateCopyright } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const copyright = await req.json()
- try {
- await updateCopyright(copyright);
- return Response.json({ code: 200, message: '更新成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '更新失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/get-copyrights/route.ts b/app/api/v1/get-copyrights/route.ts
deleted file mode 100644
index f70a30c..0000000
--- a/app/api/v1/get-copyrights/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchCopyrightList } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchCopyrightList()
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/get-custom-title/route.ts b/app/api/v1/get-custom-title/route.ts
deleted file mode 100644
index d0cf3f2..0000000
--- a/app/api/v1/get-custom-title/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchCustomTitle } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchCustomTitle();
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/get-seed-secret/route.ts b/app/api/v1/get-seed-secret/route.ts
deleted file mode 100644
index 6f11429..0000000
--- a/app/api/v1/get-seed-secret/route.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'server-only'
-import * as OTPAuth from 'otpauth'
-import { saveAuthTemplateSecret } from '~/server/lib/operate'
-
-export async function GET() {
- try {
- let secret = new OTPAuth.Secret({ size: 12 });
-
- let totp = new OTPAuth.TOTP({
- issuer: "PicImpact",
- label: "admin",
- algorithm: "SHA512",
- digits: 6,
- period: 30,
- secret: secret,
- });
-
- await saveAuthTemplateSecret(secret.base32);
-
- return Response.json({
- code: 200,
- message: '令牌颁发成功!',
- data: {
- uri: totp.toString(),
- secret: secret.base32
- }
- })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '令牌颁发失败!' })
- }
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/get-tags/route.ts b/app/api/v1/get-tags/route.ts
deleted file mode 100644
index 0bd94aa..0000000
--- a/app/api/v1/get-tags/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchTagsListAndNotDefault } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchTagsListAndNotDefault();
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/image-add/route.ts b/app/api/v1/image-add/route.ts
deleted file mode 100644
index c74dca3..0000000
--- a/app/api/v1/image-add/route.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'server-only'
-import { insertImage } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function POST(req: NextRequest) {
- const image = await req.json()
- if (!image.url) {
- return Response.json({
- code: 500,
- message: '图片链接不能为空!'
- })
- }
- if (!image.height || image.height <= 0) {
- return Response.json({
- code: 500,
- message: '图片高度不能为空且必须大于 0!'
- })
- }
- if (!image.width || image.width <= 0) {
- return Response.json({
- code: 500,
- message: '图片宽度不能为空且必须大于 0!'
- })
- }
- try {
- await insertImage(image);
- return Response.json({ code: 200, message: '保存成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '保存失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/image-batch-delete/route.ts b/app/api/v1/image-batch-delete/route.ts
deleted file mode 100644
index 5e9f63e..0000000
--- a/app/api/v1/image-batch-delete/route.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'server-only'
-import { deleteBatchImage } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function DELETE(req: NextRequest) {
- try {
- const data = await req.json()
- await deleteBatchImage(data);
- return Response.json({ code: 200, message: '删除成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '删除失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/image-delete/[id]/route.ts b/app/api/v1/image-delete/[id]/route.ts
deleted file mode 100644
index b88f56d..0000000
--- a/app/api/v1/image-delete/[id]/route.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'server-only'
-import { deleteImage } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function DELETE(
- req: NextRequest,
- { params }: { params: { id: number } },
-) {
- try {
- await deleteImage(params.id);
- return Response.json({ code: 200, message: '删除成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '删除失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/image-update/route.ts b/app/api/v1/image-update/route.ts
deleted file mode 100644
index 6f61497..0000000
--- a/app/api/v1/image-update/route.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'server-only'
-import { updateImage } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const image = await req.json()
- if (!image.url) {
- return Response.json({
- code: 500,
- message: '图片链接不能为空!'
- })
- }
- if (!image.height || image.height <= 0) {
- return Response.json({
- code: 500,
- message: '图片高度不能为空且必须大于 0!'
- })
- }
- if (!image.width || image.width <= 0) {
- return Response.json({
- code: 500,
- message: '图片宽度不能为空且必须大于 0!'
- })
- }
- try {
- await updateImage(image);
- return Response.json({ code: 200, message: '更新成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '更新失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/r2-info/route.ts b/app/api/v1/r2-info/route.ts
deleted file mode 100644
index 3ce7c92..0000000
--- a/app/api/v1/r2-info/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchR2Info } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchR2Info();
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/remove-auth/route.ts b/app/api/v1/remove-auth/route.ts
deleted file mode 100644
index 8932be7..0000000
--- a/app/api/v1/remove-auth/route.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import 'server-only'
-import { deleteAuthSecret } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function DELETE(req: NextRequest) {
- try {
- await deleteAuthSecret();
- return Response.json({ code: 200, message: '移除成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '移除失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/s3-info/route.ts b/app/api/v1/s3-info/route.ts
deleted file mode 100644
index 5ecf107..0000000
--- a/app/api/v1/s3-info/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { fetchS3Info } from '~/server/lib/query'
-
-export async function GET() {
- const data = await fetchS3Info();
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
diff --git a/app/api/v1/tag-add/route.ts b/app/api/v1/tag-add/route.ts
deleted file mode 100644
index 9b124cd..0000000
--- a/app/api/v1/tag-add/route.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'server-only'
-import { insertTag } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function POST(req: NextRequest) {
- const tag = await req.json()
- if (tag.tag_value && tag.tag_value.charAt(0) !== '/') {
- return Response.json({
- code: 500,
- message: '路由必须以 / 开头!'
- })
- }
- try {
- await insertTag(tag);
- return Response.json({ code: 200, message: '新增成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '新增失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/tag-delete/[id]/route.ts b/app/api/v1/tag-delete/[id]/route.ts
deleted file mode 100644
index 776faf2..0000000
--- a/app/api/v1/tag-delete/[id]/route.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'server-only'
-import { deleteTag } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function DELETE(
- req: NextRequest,
- { params }: { params: { id: number } },
-) {
- const data = await deleteTag(params.id);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/tag-update/route.ts b/app/api/v1/tag-update/route.ts
deleted file mode 100644
index 24b12f4..0000000
--- a/app/api/v1/tag-update/route.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'server-only'
-import { updateTag } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const tag = await req.json()
- if (tag.tag_value && tag.tag_value.charAt(0) !== '/') {
- return Response.json({
- code: 500,
- message: '路由必须以 / 开头!'
- })
- }
- try {
- await updateTag(tag);
- return Response.json({ code: 200, message: '更新成功!' })
- } catch (e) {
- console.log(e)
- return Response.json({ code: 500, message: '更新失败!' })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/update-alist-info/route.ts b/app/api/v1/update-alist-info/route.ts
deleted file mode 100644
index dd62047..0000000
--- a/app/api/v1/update-alist-info/route.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'server-only'
-import { updateAListConfig } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-import { Config } from '~/types'
-
-export async function PUT(req: NextRequest) {
- const query = await req.json()
-
- const alistUrl = query?.find((item: Config) => item.config_key === 'alist_url').config_value
- const alistToken = query?.find((item: Config) => item.config_key === 'alist_token').config_value
-
- const data = await updateAListConfig({ alistUrl, alistToken });
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-copyright-default/route.ts b/app/api/v1/update-copyright-default/route.ts
deleted file mode 100644
index 17cc489..0000000
--- a/app/api/v1/update-copyright-default/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { updateCopyrightDefault } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const copyright = await req.json()
- const data = await updateCopyrightDefault(copyright.id, copyright.default);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-copyright-show/route.ts b/app/api/v1/update-copyright-show/route.ts
deleted file mode 100644
index 4bc89c4..0000000
--- a/app/api/v1/update-copyright-show/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { updateCopyrightShow } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const copyright = await req.json()
- const data = await updateCopyrightShow(copyright.id, copyright.show);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-custom-title/route.ts b/app/api/v1/update-custom-title/route.ts
deleted file mode 100644
index 97925a5..0000000
--- a/app/api/v1/update-custom-title/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { updateCustomTitle } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const query = await req.json()
- const data = await updateCustomTitle(query.title);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-image-show/route.ts b/app/api/v1/update-image-show/route.ts
deleted file mode 100644
index ecd14d8..0000000
--- a/app/api/v1/update-image-show/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { updateImageShow } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const image = await req.json()
- const data = await updateImageShow(image.id, image.show);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-image-tag/route.ts b/app/api/v1/update-image-tag/route.ts
deleted file mode 100644
index 7f78bd3..0000000
--- a/app/api/v1/update-image-tag/route.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'server-only'
-import { updateImageTag } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const image = await req.json()
- try {
- await updateImageTag(image.imageId, image.tagId);
- return Response.json({
- code: 200,
- message: '更新成功!'
- })
- } catch (e) {
- console.log(e)
- return Response.json({
- code: 500,
- message: '更新失败!'
- })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/update-password/route.ts b/app/api/v1/update-password/route.ts
deleted file mode 100644
index 9c76d63..0000000
--- a/app/api/v1/update-password/route.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'server-only'
-import { updatePassword } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-import { auth } from '~/server/auth'
-import CryptoJS from 'crypto-js'
-import { fetchSecretKey, fetchUserById } from '~/server/lib/query'
-
-export async function PUT(req: NextRequest) {
- const { user } = await auth()
- const pwd = await req.json()
- const daUser = await fetchUserById(user?.id)
- const secretKey = await fetchSecretKey()
- if (!secretKey || !secretKey.config_value) {
- return Response.json({
- code: 500,
- message: '更新失败!'
- })
- }
- const hashedOldPassword = CryptoJS.HmacSHA512(pwd.oldPassword, secretKey?.config_value).toString()
-
- try {
- if (daUser && hashedOldPassword === daUser.password) {
- const hashedNewPassword = CryptoJS.HmacSHA512(pwd.newPassword, secretKey?.config_value).toString()
- const data = await updatePassword(user?.id, hashedNewPassword);
- return Response.json({
- code: 200,
- message: '更新成功!'
- })
- } else {
- return Response.json({
- code: 500,
- message: '旧密码不匹配!'
- })
- }
- } catch (e) {
- console.log(e)
- return Response.json({
- code: 500,
- message: '更新失败!'
- })
- }
-}
\ No newline at end of file
diff --git a/app/api/v1/update-r2-info/route.ts b/app/api/v1/update-r2-info/route.ts
deleted file mode 100644
index 4a09e3b..0000000
--- a/app/api/v1/update-r2-info/route.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import 'server-only'
-import { updateR2Config } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-import { Config } from '~/types'
-
-export async function PUT(req: NextRequest) {
- const query = await req.json()
-
- const r2AccesskeyId = query?.find((item: Config) => item.config_key === 'r2_accesskey_id').config_value
- const r2AccesskeySecret = query?.find((item: Config) => item.config_key === 'r2_accesskey_secret').config_value
- const r2Endpoint = query?.find((item: Config) => item.config_key === 'r2_endpoint').config_value
- const r2Bucket = query?.find((item: Config) => item.config_key === 'r2_bucket').config_value
- const r2StorageFolder = query?.find((item: Config) => item.config_key === 'r2_storage_folder').config_value
- const r2PublicDomain = query?.find((item: Config) => item.config_key === 'r2_public_domain').config_value
-
- const data = await updateR2Config({ r2AccesskeyId, r2AccesskeySecret, r2Endpoint, r2Bucket, r2StorageFolder, r2PublicDomain });
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-s3-info/route.ts b/app/api/v1/update-s3-info/route.ts
deleted file mode 100644
index 3b95315..0000000
--- a/app/api/v1/update-s3-info/route.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import 'server-only'
-import { updateS3Config } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-import { Config } from '~/types'
-
-export async function PUT(req: NextRequest) {
- const query = await req.json()
-
- const accesskeyId = query?.find((item: Config) => item.config_key === 'accesskey_id').config_value
- const accesskeySecret = query?.find((item: Config) => item.config_key === 'accesskey_secret').config_value
- const region = query?.find((item: Config) => item.config_key === 'region').config_value
- const endpoint = query?.find((item: Config) => item.config_key === 'endpoint').config_value
- const bucket = query?.find((item: Config) => item.config_key === 'bucket').config_value
- const storageFolder = query?.find((item: Config) => item.config_key === 'storage_folder').config_value
- const forcePathStyle = query?.find((item: Config) => item.config_key === 'force_path_style').config_value
- const s3Cdn = query?.find((item: Config) => item.config_key === 's3_cdn').config_value
- const s3CdnUrl = query?.find((item: Config) => item.config_key === 's3_cdn_url').config_value
-
- const data = await updateS3Config({ accesskeyId, accesskeySecret, region, endpoint, bucket, storageFolder, forcePathStyle, s3Cdn, s3CdnUrl });
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/api/v1/update-tag-show/route.ts b/app/api/v1/update-tag-show/route.ts
deleted file mode 100644
index 23e0dfb..0000000
--- a/app/api/v1/update-tag-show/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'server-only'
-import { updateTagShow } from '~/server/lib/operate'
-import { NextRequest } from 'next/server'
-
-export async function PUT(req: NextRequest) {
- const tag = await req.json()
- const data = await updateTagShow(tag.id, tag.show);
- return Response.json(data)
-}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index e99ab88..1648417 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -7,7 +7,7 @@ import { ProgressBarProviders } from '~/app/providers/progress-bar-providers'
import { ButtonStoreProvider } from '~/app/providers/button-store-Providers'
import '~/style/globals.css'
-import { fetchCustomTitle } from '~/server/lib/query'
+import { fetchCustomTitle } from '~/server/db/query'
type Props = {
params: { id: string }
@@ -33,7 +33,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
diff --git a/app/providers/next-ui-providers.tsx b/app/providers/next-ui-providers.tsx
index d8d388b..3f4154d 100644
--- a/app/providers/next-ui-providers.tsx
+++ b/app/providers/next-ui-providers.tsx
@@ -6,7 +6,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes'
export function NextUIProviders({children}: { children: React.ReactNode }) {
return (
-
+
{children}
diff --git a/components.json b/components.json
index f2fb0af..ce140ea 100644
--- a/components.json
+++ b/components.json
@@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
+ "style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
@@ -12,6 +12,9 @@
},
"aliases": {
"components": "~/components",
- "utils": "~/lib/utils"
+ "utils": "~/utils",
+ "ui": "~/components/ui",
+ "lib": "~/utils",
+ "hooks": "~/hooks"
}
}
\ No newline at end of file
diff --git a/components/MasonryItem.tsx b/components/MasonryItem.tsx
index 6b9d967..029ae35 100644
--- a/components/MasonryItem.tsx
+++ b/components/MasonryItem.tsx
@@ -133,7 +133,7 @@ export default function MasonryItem() {
}
onClick={() => loadingHandle('prev')}
>
@@ -142,7 +142,7 @@ export default function MasonryItem() {
}
onClick={() => loadingHandle('next')}
>
@@ -164,8 +164,8 @@ export default function MasonryItem() {
{MasonryViewData?.exif?.model && MasonryViewData?.exif?.f_number
diff --git a/components/admin/copyright/CopyrightAddSheet.tsx b/components/admin/copyright/CopyrightAddSheet.tsx
index ef0a61f..2e60fea 100644
--- a/components/admin/copyright/CopyrightAddSheet.tsx
+++ b/components/admin/copyright/CopyrightAddSheet.tsx
@@ -28,7 +28,7 @@ export default function CopyrightAddSheet(props : Readonly) {
}
try {
setLoading(true)
- const res = await fetch('/api/v1/copyright-add', {
+ const res = await fetch('/api/v1/copyright/add', {
headers: {
'Content-Type': 'application/json',
},
diff --git a/components/admin/copyright/CopyrightEditSheet.tsx b/components/admin/copyright/CopyrightEditSheet.tsx
index fb71444..5421037 100644
--- a/components/admin/copyright/CopyrightEditSheet.tsx
+++ b/components/admin/copyright/CopyrightEditSheet.tsx
@@ -27,7 +27,7 @@ export default function CopyrightEditSheet(props : Readonly) {
}
try {
setLoading(true)
- const res = await fetch('/api/v1/copyright-update', {
+ const res = await fetch('/api/v1/copyright/update', {
headers: {
'Content-Type': 'application/json',
},
diff --git a/components/admin/copyright/CopyrightList.tsx b/components/admin/copyright/CopyrightList.tsx
index efd0b2c..e489d85 100644
--- a/components/admin/copyright/CopyrightList.tsx
+++ b/components/admin/copyright/CopyrightList.tsx
@@ -43,7 +43,7 @@ export default function CopyrightList(props : Readonly) {
setDeleteLoading(true)
if (!copyright.id) return
try {
- const res = await fetch(`/api/v1/copyright-delete/${copyright.id}`, {
+ const res = await fetch(`/api/v1/copyright/delete/${copyright.id}`, {
method: 'DELETE',
})
if (res.status === 200) {
@@ -64,7 +64,7 @@ export default function CopyrightList(props : Readonly) {
try {
setUpdateCopyrightId(id)
setUpdateCopyrightLoading(true)
- const res = await fetch(`/api/v1/update-copyright-show`, {
+ const res = await fetch(`/api/v1/copyright/update-show`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -92,7 +92,7 @@ export default function CopyrightList(props : Readonly) {
try {
setUpdateCopyrightDefaultId(id)
setUpdateCopyrightDefaultLoading(true)
- const res = await fetch(`/api/v1/update-copyright-default`, {
+ const res = await fetch(`/api/v1/copyright/update-default`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/components/admin/list/ImageBatchDeleteSheet.tsx b/components/admin/list/ImageBatchDeleteSheet.tsx
index 8f7e9d5..0c0aee0 100644
--- a/components/admin/list/ImageBatchDeleteSheet.tsx
+++ b/components/admin/list/ImageBatchDeleteSheet.tsx
@@ -29,7 +29,7 @@ export default function ImageBatchDeleteSheet(props : Readonly state,
)
const [loading, setLoading] = useState(false)
- const { data, isLoading } = useSWR('/api/v1/get-copyrights', fetcher)
+ const { data, isLoading } = useSWR('/api/v1/copyright/get', fetcher)
async function submit() {
if (!image.url) {
@@ -35,7 +35,7 @@ export default function ImageEditSheet(props : Readonly state,
)
- const { data, isLoading } = useSWR('/api/v1/get-copyrights', fetcher)
+ const { data, isLoading } = useSWR('/api/v1/copyright/get', fetcher)
const fieldNames = { label: 'name', value: 'id' }
diff --git a/components/admin/list/ListProps.tsx b/components/admin/list/ListProps.tsx
index 6813851..c996c90 100644
--- a/components/admin/list/ListProps.tsx
+++ b/components/admin/list/ListProps.tsx
@@ -61,7 +61,7 @@ export default function ListProps(props : Readonly) {
const { setImageEdit, setImageEditData, setImageView, setImageViewData, setImageHelp, setImageBatchDelete } = useButtonStore(
(state) => state,
)
- const { data: tags, isLoading: tagsLoading } = useSWR('/api/v1/get-tags', fetcher)
+ const { data: tags, isLoading: tagsLoading } = useSWR('/api/v1/tags/get', fetcher)
const dataProps: DataProps = {
data: data,
@@ -71,7 +71,7 @@ export default function ListProps(props : Readonly) {
setDeleteLoading(true)
if (!image.id) return
try {
- const res = await fetch(`/api/v1/image-delete/${image.id}`, {
+ const res = await fetch(`/api/v1/image/delete/${image.id}`, {
method: 'DELETE',
}).then(res => res.json())
if (res?.code === 200) {
@@ -92,7 +92,7 @@ export default function ListProps(props : Readonly) {
try {
setUpdateShowLoading(true)
setUpdateShowId(id)
- const res = await fetch(`/api/v1/update-image-show`, {
+ const res = await fetch(`/api/v1/image/update-show`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -123,7 +123,7 @@ export default function ListProps(props : Readonly) {
}
try {
setUpdateImageTagLoading(true)
- const res = await fetch(`/api/v1/update-image-tag`, {
+ const res = await fetch(`/api/v1/image/update-tag`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/components/admin/settings/storages/AListEditSheet.tsx b/components/admin/settings/storages/AListEditSheet.tsx
index 52c9970..5ce45c1 100644
--- a/components/admin/settings/storages/AListEditSheet.tsx
+++ b/components/admin/settings/storages/AListEditSheet.tsx
@@ -18,7 +18,7 @@ export default function AListEditSheet() {
async function submit() {
setLoading(true)
try {
- const res = await fetch('/api/v1/update-alist-info', {
+ const res = await fetch('/api/v1/settings/update-alist-info', {
headers: {
'Content-Type': 'application/json',
},
@@ -26,7 +26,7 @@ export default function AListEditSheet() {
body: JSON.stringify(aListData),
}).then(res => res.json())
toast.success('更新成功!')
- mutate('/api/v1/alist-info')
+ mutate('/api/v1/storage/alist/info')
} catch (e) {
toast.error('更新失败!')
} finally {
diff --git a/components/admin/settings/storages/AListTabs.tsx b/components/admin/settings/storages/AListTabs.tsx
index 76a0707..269461a 100644
--- a/components/admin/settings/storages/AListTabs.tsx
+++ b/components/admin/settings/storages/AListTabs.tsx
@@ -7,7 +7,7 @@ import { toast } from 'sonner'
import { useButtonStore } from '~/app/providers/button-store-Providers'
export default function AListTabs() {
- const { data, error, isValidating, mutate } = useSWR('/api/v1/alist-info', fetcher
+ const { data, error, isValidating, mutate } = useSWR('/api/v1/storage/alist/info', fetcher
, { revalidateOnFocus: false })
const { setAListEdit, setAListEditData } = useButtonStore(
(state) => state,
diff --git a/components/admin/settings/storages/R2EditSheet.tsx b/components/admin/settings/storages/R2EditSheet.tsx
index b4bfcf9..b6a8e3e 100644
--- a/components/admin/settings/storages/R2EditSheet.tsx
+++ b/components/admin/settings/storages/R2EditSheet.tsx
@@ -18,7 +18,7 @@ export default function S3EditSheet() {
async function submit() {
setLoading(true)
try {
- await fetch('/api/v1/update-r2-info', {
+ await fetch('/api/v1/settings/update-r2-info', {
headers: {
'Content-Type': 'application/json',
},
@@ -26,7 +26,7 @@ export default function S3EditSheet() {
body: JSON.stringify(r2Data),
}).then(res => res.json())
toast.success('更新成功!')
- mutate('/api/v1/r2-info')
+ mutate('/api/v1/settings/r2-info')
} catch (e) {
toast.error('更新失败!')
} finally {
diff --git a/components/admin/settings/storages/R2Tabs.tsx b/components/admin/settings/storages/R2Tabs.tsx
index 4e7e53a..006ae7b 100644
--- a/components/admin/settings/storages/R2Tabs.tsx
+++ b/components/admin/settings/storages/R2Tabs.tsx
@@ -7,7 +7,7 @@ import { toast } from 'sonner'
import { useButtonStore } from '~/app/providers/button-store-Providers'
export default function R2Tabs() {
- const { data, error, isValidating, mutate } = useSWR('/api/v1/r2-info', fetcher
+ const { data, error, isValidating, mutate } = useSWR('/api/v1/settings/r2-info', fetcher
, { revalidateOnFocus: false })
const { setR2Edit, setR2EditData } = useButtonStore(
(state) => state,
diff --git a/components/admin/settings/storages/S3EditSheet.tsx b/components/admin/settings/storages/S3EditSheet.tsx
index fd7a8ce..a0c4384 100644
--- a/components/admin/settings/storages/S3EditSheet.tsx
+++ b/components/admin/settings/storages/S3EditSheet.tsx
@@ -18,7 +18,7 @@ export default function S3EditSheet() {
async function submit() {
setLoading(true)
try {
- const res = await fetch('/api/v1/update-s3-info', {
+ const res = await fetch('/api/v1/settings/update-s3-info', {
headers: {
'Content-Type': 'application/json',
},
@@ -26,7 +26,7 @@ export default function S3EditSheet() {
body: JSON.stringify(s3Data),
}).then(res => res.json())
toast.success('更新成功!')
- mutate('/api/v1/s3-info')
+ mutate('/api/v1/settings/s3-info')
} catch (e) {
toast.error('更新失败!')
} finally {
diff --git a/components/admin/settings/storages/S3Tabs.tsx b/components/admin/settings/storages/S3Tabs.tsx
index 87dd835..6861bd4 100644
--- a/components/admin/settings/storages/S3Tabs.tsx
+++ b/components/admin/settings/storages/S3Tabs.tsx
@@ -7,7 +7,7 @@ import { toast } from 'sonner'
import { useButtonStore } from '~/app/providers/button-store-Providers'
export default function S3Tabs() {
- const { data, error, isValidating, mutate } = useSWR('/api/v1/s3-info', fetcher
+ const { data, error, isValidating, mutate } = useSWR('/api/v1/settings/s3-info', fetcher
, { revalidateOnFocus: false })
const { setS3Edit, setS3EditData } = useButtonStore(
(state) => state,
diff --git a/components/admin/tag/TagAddSheet.tsx b/components/admin/tag/TagAddSheet.tsx
index 42b3fa0..a492139 100644
--- a/components/admin/tag/TagAddSheet.tsx
+++ b/components/admin/tag/TagAddSheet.tsx
@@ -27,7 +27,7 @@ export default function TagAddSheet(props : Readonly) {
}
try {
setLoading(true)
- const res = await fetch('/api/v1/tag-add', {
+ const res = await fetch('/api/v1/tags/add', {
headers: {
'Content-Type': 'application/json',
},
diff --git a/components/admin/tag/TagEditSheet.tsx b/components/admin/tag/TagEditSheet.tsx
index 29ef60e..d8f064b 100644
--- a/components/admin/tag/TagEditSheet.tsx
+++ b/components/admin/tag/TagEditSheet.tsx
@@ -26,7 +26,7 @@ export default function TagEditSheet(props : Readonly) {
}
try {
setLoading(true)
- const res = await fetch('/api/v1/tag-update', {
+ const res = await fetch('/api/v1/tags/update', {
headers: {
'Content-Type': 'application/json',
},
diff --git a/components/admin/tag/TagList.tsx b/components/admin/tag/TagList.tsx
index c55f96f..b0d6c87 100644
--- a/components/admin/tag/TagList.tsx
+++ b/components/admin/tag/TagList.tsx
@@ -43,7 +43,7 @@ export default function TagList(props : Readonly) {
setDeleteLoading(true)
if (!tag.id) return
try {
- const res = await fetch(`/api/v1/tag-delete/${tag.id}`, {
+ const res = await fetch(`/api/v1/tags/delete/${tag.id}`, {
method: 'DELETE',
})
if (res.status === 200) {
@@ -64,7 +64,7 @@ export default function TagList(props : Readonly) {
try {
setUpdateTagId(id)
setUpdateTagLoading(true)
- const res = await fetch(`/api/v1/update-tag-show`, {
+ const res = await fetch(`/api/v1/tags/update-show`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/components/admin/upload/FileUpload.tsx b/components/admin/upload/FileUpload.tsx
index f85bbdc..d7123f2 100644
--- a/components/admin/upload/FileUpload.tsx
+++ b/components/admin/upload/FileUpload.tsx
@@ -44,7 +44,7 @@ export default function FileUpload() {
(state) => state,
)
- const { data, isLoading } = useSWR('/api/v1/get-tags', fetcher)
+ const { data, isLoading } = useSWR('/api/v1/tags/get', fetcher)
async function loadExif(file: any) {
try {
@@ -137,7 +137,7 @@ export default function FileUpload() {
lat: lat,
lon: lon,
} as ImageType
- const res = await fetch('/api/v1/image-add', {
+ const res = await fetch('/api/v1/image/add', {
headers: {
'Content-Type': 'application/json',
},
@@ -185,7 +185,7 @@ export default function FileUpload() {
}
try {
toast.info('正在获取 AList 挂载目录!')
- const res = await fetch('/api/v1/alist-storages', {
+ const res = await fetch('/api/v1/storage/alist/storages', {
method: 'GET',
}).then(res => res.json())
if (res?.code === 200) {
@@ -224,7 +224,7 @@ export default function FileUpload() {
formData.append('storage', storageArray[0])
formData.append('type', type)
formData.append('mountPath', alistMountPathArray[0])
- return await fetch('/api/v1/file-upload', {
+ return await fetch('/api/v1/file/upload', {
method: 'POST',
body: formData
}).then((res) => res.json())
@@ -299,7 +299,7 @@ export default function FileUpload() {
lat: '',
lon: '',
} as ImageType
- const res = await fetch('/api/v1/image-add', {
+ const res = await fetch('/api/v1/image/add', {
headers: {
'Content-Type': 'application/json',
},
diff --git a/components/layout/DropMenu.tsx b/components/layout/DropMenu.tsx
index bb8f638..f4d9990 100644
--- a/components/layout/DropMenu.tsx
+++ b/components/layout/DropMenu.tsx
@@ -1,13 +1,18 @@
'use client'
-import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Avatar } from '@nextui-org/react'
+import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@nextui-org/react'
import { usePathname } from 'next/navigation'
import { useRouter } from 'next-nprogress-bar'
-import { loginOut } from '~/server/lib/actions'
+import { loginOut } from '~/server/actions'
import { useSession } from 'next-auth/react'
import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
-import { Home, MonitorDot, SunMedium, MoonStar, Github, LogOut, LogIn } from 'lucide-react'
+import { Home, MonitorDot, SunMedium, MoonStar, Github, LogOut, LogIn, Orbit } from 'lucide-react'
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "~/components/ui/avatar"
export const DropMenu = () => {
const router = useRouter()
@@ -27,13 +32,13 @@ export const DropMenu = () => {
return (
-
+
+
+ CN
+
{
session ?
@@ -76,7 +81,7 @@ export const DropMenu = () => {
: }
+ startContent={theme === 'light' ? : }
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
{ theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' }
@@ -101,7 +106,7 @@ export const DropMenu = () => {
: }
+ startContent={theme === 'light' ? : }
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
{ theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' }
diff --git a/components/layout/DynamicNavbar.tsx b/components/layout/DynamicNavbar.tsx
index 4e096bc..529bbf9 100644
--- a/components/layout/DynamicNavbar.tsx
+++ b/components/layout/DynamicNavbar.tsx
@@ -1,7 +1,7 @@
import VaulDrawer from '~/components/layout/VaulDrawer'
import { DropMenu } from '~/components/layout/DropMenu'
import DynamicDropMenu from '~/components/layout/DynamicDropMenu'
-import { fetchTagsShow } from '~/server/lib/query'
+import { fetchTagsShow } from '~/server/db/query'
import { DataProps } from '~/types'
import SearchButton from '~/components/admin/SearchButton'
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx
index 9fdd48f..e262699 100644
--- a/components/layout/Header.tsx
+++ b/components/layout/Header.tsx
@@ -2,7 +2,7 @@ import { Navbar, NavbarBrand, NavbarContent, NavbarItem } from '@nextui-org/reac
import Logo from '~/components/layout/Logo'
import DynamicNavbar from '~/components/layout/DynamicNavbar'
import HeaderLink from '~/components/layout/HeaderLink'
-import { fetchTagsShow } from '~/server/lib/query'
+import { fetchTagsShow } from '~/server/db/query'
import { DataProps } from '~/types'
export default async function Header() {
diff --git a/components/layout/VaulDrawer.tsx b/components/layout/VaulDrawer.tsx
index b7714e5..609d33f 100644
--- a/components/layout/VaulDrawer.tsx
+++ b/components/layout/VaulDrawer.tsx
@@ -19,9 +19,10 @@ import {
Settings,
LogOut,
Copyright,
- Info
+ Info,
+ Orbit,
} from 'lucide-react'
-import { loginOut } from '~/server/lib/actions'
+import { loginOut } from '~/server/actions'
export default function VaulDrawer() {
const { data: session, status } = useSession()
@@ -150,7 +151,7 @@ export default function VaulDrawer() {
: }
+ startContent={theme === 'light' ? : }
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
{ theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' }
@@ -179,7 +180,7 @@ export default function VaulDrawer() {
: }
+ startContent={theme === 'light' ? : }
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
{ theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' }
diff --git a/components/login/UserFrom.tsx b/components/login/UserFrom.tsx
index 9d11773..0e86001 100644
--- a/components/login/UserFrom.tsx
+++ b/components/login/UserFrom.tsx
@@ -4,7 +4,7 @@ import React, { useState } from 'react'
import { Button, Input } from '@nextui-org/react'
import { useRouter } from 'next-nprogress-bar'
import { toast } from 'sonner'
-import { authenticate } from '~/server/lib/actions'
+import { authenticate } from '~/server/actions'
import { SafeParseReturnType, z } from 'zod'
import confetti from 'canvas-confetti'
import { Eye, EyeOff } from 'lucide-react'
diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx
new file mode 100644
index 0000000..04c5795
--- /dev/null
+++ b/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+'use client'
+
+import * as React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+
+import { cn } from '~/utils'
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/hono/auth.ts b/hono/auth.ts
new file mode 100644
index 0000000..d4105e3
--- /dev/null
+++ b/hono/auth.ts
@@ -0,0 +1,74 @@
+import 'server-only'
+import { queryAuthTemplateSecret } from '~/server/db/query'
+import * as OTPAuth from 'otpauth'
+import { deleteAuthSecret, saveAuthSecret, saveAuthTemplateSecret } from '~/server/db/operate'
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/get-seed-secret', async (c) => {
+ try {
+ let secret = new OTPAuth.Secret({ size: 12 });
+
+ let totp = new OTPAuth.TOTP({
+ issuer: "PicImpact",
+ label: "admin",
+ algorithm: "SHA512",
+ digits: 6,
+ period: 30,
+ secret: secret,
+ });
+
+ await saveAuthTemplateSecret(secret.base32);
+
+ return c.json({
+ code: 200,
+ message: '令牌颁发成功!',
+ data: {
+ uri: totp.toString(),
+ secret: secret.base32
+ }
+ })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '令牌颁发失败!' })
+ }
+})
+
+app.post('/validate', async (c) => {
+ const data = await c.req.json()
+ try {
+ const secret = await queryAuthTemplateSecret();
+ let totp = new OTPAuth.TOTP({
+ issuer: "PicImpact",
+ label: "admin",
+ algorithm: "SHA512",
+ digits: 6,
+ period: 30,
+ // @ts-ignore
+ secret: OTPAuth.Secret.fromBase32(secret?.config_value),
+ });
+ let delta = totp.validate({ token: data.token, window: 1 })
+ if (delta === 0) {
+ // @ts-ignore
+ await saveAuthSecret('true', secret?.config_value)
+ return c.json({ code: 200, message: '设置成功!' })
+ }
+ return c.json({ code: 500, message: '设置失败!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '设置失败!' })
+ }
+})
+
+app.delete('/remove', async (c) => {
+ try {
+ await deleteAuthSecret();
+ return c.json({ code: 200, message: '移除成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '移除失败!' })
+ }
+})
+
+export default app
\ No newline at end of file
diff --git a/hono/copyright.ts b/hono/copyright.ts
new file mode 100644
index 0000000..458dafc
--- /dev/null
+++ b/hono/copyright.ts
@@ -0,0 +1,59 @@
+import 'server-only'
+import {
+ deleteCopyright,
+ insertCopyright,
+ updateCopyright,
+ updateCopyrightDefault,
+ updateCopyrightShow
+} from '~/server/db/operate'
+import { fetchCopyrightList } from '~/server/db/query'
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/get', async (c) => {
+ const data = await fetchCopyrightList()
+ return c.json(data)
+})
+
+app.post('/add', async (c) => {
+ const copyright = await c.req.json()
+ try {
+ await insertCopyright(copyright);
+ return c.json({ code: 200, message: '新增成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '新增失败!' })
+ }
+})
+
+app.delete('/delete/:id', async (c) => {
+ const { id } = c.req.param()
+ const data = await deleteCopyright(Number(id));
+ return c.json(data)
+})
+
+app.put('/update', async (c) => {
+ const copyright = await c.req.json()
+ try {
+ await updateCopyright(copyright);
+ return c.json({ code: 200, message: '更新成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '更新失败!' })
+ }
+})
+
+app.put('/update-default', async (c) => {
+ const copyright = await c.req.json()
+ const data = await updateCopyrightDefault(copyright.id, copyright.default);
+ return c.json(data)
+})
+
+app.put('/update-show', async (c) => {
+ const copyright = await c.req.json()
+ const data = await updateCopyrightShow(copyright.id, copyright.show);
+ return c.json(data)
+})
+
+export default app
diff --git a/app/api/v1/file-upload/route.ts b/hono/file.ts
similarity index 79%
rename from app/api/v1/file-upload/route.ts
rename to hono/file.ts
index 71016c9..2d4d09e 100644
--- a/app/api/v1/file-upload/route.ts
+++ b/hono/file.ts
@@ -1,10 +1,14 @@
-import { fetchAListInfo, fetchS3Info, fetchR2Info } from '~/server/lib/query'
+import 'server-only'
+import { fetchAListInfo, fetchS3Info, fetchR2Info } from '~/server/db/query'
import { getClient } from '~/server/lib/s3'
import { getR2Client } from '~/server/lib/r2'
import { PutObjectCommand } from '@aws-sdk/client-s3'
+import { Hono } from 'hono'
-export async function POST(request: Request) {
- const formData = await request.formData()
+const app = new Hono()
+
+app.post('/upload', async (c) => {
+ const formData = await c.req.formData()
const file = formData.get('file')
const storage = formData.get('storage')
@@ -40,7 +44,8 @@ export async function POST(request: Request) {
)
if (s3Cdn && s3Cdn === 'true') {
- return Response.json({ code: 200, data: `https://${
+ return Response.json({
+ code: 200, data: `https://${
s3CdnUrl.includes('https://') ? s3CdnUrl.split('//')[1] : s3CdnUrl
}/${
storageFolder && storageFolder !== '/'
@@ -50,7 +55,8 @@ export async function POST(request: Request) {
})
} else {
if (forcePathStyle && forcePathStyle === 'true') {
- return Response.json({ code: 200, data: `https://${
+ return Response.json({
+ code: 200, data: `https://${
endpoint.includes('https://') ? endpoint.split('//')[1] : endpoint
}/${bucket}/${
storageFolder && storageFolder !== '/'
@@ -60,12 +66,13 @@ export async function POST(request: Request) {
})
}
}
- return Response.json({ code: 200, data: `https://${bucket}.${
- endpoint.includes('https://') ? endpoint.split('//')[1] : endpoint
- }/${
- storageFolder && storageFolder !== '/'
- ? type && type !== '/' ? `${storageFolder}${type}/${encodeURIComponent(file?.name)}` : `${storageFolder}/${encodeURIComponent(file?.name)}`
- : type && type !== '/' ? `${type.slice(1)}/${encodeURIComponent(file?.name)}` : `${encodeURIComponent(file?.name)}`
+ return Response.json({
+ code: 200, data: `https://${bucket}.${
+ endpoint.includes('https://') ? endpoint.split('//')[1] : endpoint
+ }/${
+ storageFolder && storageFolder !== '/'
+ ? type && type !== '/' ? `${storageFolder}${type}/${encodeURIComponent(file?.name)}` : `${storageFolder}/${encodeURIComponent(file?.name)}`
+ : type && type !== '/' ? `${type.slice(1)}/${encodeURIComponent(file?.name)}` : `${encodeURIComponent(file?.name)}`
}`
})
} else if (storage && storage.toString() === 'r2') {
@@ -95,10 +102,11 @@ export async function POST(request: Request) {
await r2.send(
new PutObjectCommand(params)
)
- return Response.json({ code: 200, data: `${
- r2PublicDomain ?
- r2PublicDomain.includes('https://') ? r2PublicDomain : `https://${r2PublicDomain}`
- : r2Endpoint.includes('https://') ? r2Endpoint : `https://${r2Endpoint}`
+ return Response.json({
+ code: 200, data: `${
+ r2PublicDomain ?
+ r2PublicDomain.includes('https://') ? r2PublicDomain : `https://${r2PublicDomain}`
+ : r2Endpoint.includes('https://') ? r2Endpoint : `https://${r2Endpoint}`
}/${
r2StorageFolder && r2StorageFolder !== '/'
? type && type !== '/' ? `${r2StorageFolder}${type}/${encodeURIComponent(file?.name)}` : `${r2StorageFolder}/${encodeURIComponent(file?.name)}`
@@ -110,7 +118,7 @@ export async function POST(request: Request) {
const alistToken = findConfig.find((item: any) => item.config_key === 'alist_token')?.config_value || '';
const alistUrl = findConfig.find((item: any) => item.config_key === 'alist_url')?.config_value || '';
const filePath = encodeURIComponent(`${mountPath && mountPath.toString() === '/' ? '' : mountPath}${
- type && type !== '/' ? `${type}/${file?.name}` : `/${file?.name}` }`)
+ type && type !== '/' ? `${type}/${file?.name}` : `/${file?.name}`}`)
const data = await fetch(`${alistUrl}/api/fs/put`, {
method: 'PUT',
headers: {
@@ -126,14 +134,16 @@ export async function POST(request: Request) {
'Authorization': alistToken.toString(),
'Content-Type': 'application/json'
},
- body: JSON.stringify({ path: decodeURIComponent(filePath) })
+ body: JSON.stringify({path: decodeURIComponent(filePath)})
}).then((res) => res.json())
if (res?.code === 200) {
- return Response.json({ code: 200, message: '文件上传成功!', data: res?.data.raw_url })
+ return Response.json({code: 200, message: '文件上传成功!', data: res?.data.raw_url})
} else {
- return Response.json({ code: 500, message: '文件路径获取失败!', data: null })
+ return Response.json({code: 500, message: '文件路径获取失败!', data: null})
}
}
}
- return Response.json({ code: 500, message: '文件上传失败!', data: null })
-}
\ No newline at end of file
+ return Response.json({code: 500, message: '文件上传失败!', data: null})
+})
+
+export default app
\ No newline at end of file
diff --git a/hono/hello.ts b/hono/hello.ts
new file mode 100644
index 0000000..a56b2ae
--- /dev/null
+++ b/hono/hello.ts
@@ -0,0 +1,11 @@
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/hello', (c) => {
+ return c.json({
+ message: 'Hello from Hono!',
+ })
+})
+
+export default app
diff --git a/hono/image.ts b/hono/image.ts
new file mode 100644
index 0000000..102723e
--- /dev/null
+++ b/hono/image.ts
@@ -0,0 +1,117 @@
+import 'server-only'
+import {
+ deleteBatchImage,
+ deleteImage,
+ insertImage,
+ updateImage,
+ updateImageShow,
+ updateImageTag
+} from '~/server/db/operate'
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.post('/add', async (c) => {
+ const image = await c.req.json()
+ if (!image.url) {
+ return c.json({
+ code: 500,
+ message: '图片链接不能为空!'
+ })
+ }
+ if (!image.height || image.height <= 0) {
+ return c.json({
+ code: 500,
+ message: '图片高度不能为空且必须大于 0!'
+ })
+ }
+ if (!image.width || image.width <= 0) {
+ return c.json({
+ code: 500,
+ message: '图片宽度不能为空且必须大于 0!'
+ })
+ }
+ try {
+ await insertImage(image);
+ return c.json({ code: 200, message: '保存成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '保存失败!' })
+ }
+})
+
+app.delete('/batch-delete', async (c) => {
+ try {
+ const data = await c.req.json()
+ await deleteBatchImage(data);
+ return c.json({ code: 200, message: '删除成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '删除失败!' })
+ }
+})
+
+app.delete('/delete/:id', async (c) => {
+ try {
+ const { id } = c.req.param()
+ await deleteImage(Number(id));
+ return c.json({ code: 200, message: '删除成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '删除失败!' })
+ }
+})
+
+app.put('/update', async (c) => {
+ const image = await c.req.json()
+ if (!image.url) {
+ return c.json({
+ code: 500,
+ message: '图片链接不能为空!'
+ })
+ }
+ if (!image.height || image.height <= 0) {
+ return c.json({
+ code: 500,
+ message: '图片高度不能为空且必须大于 0!'
+ })
+ }
+ if (!image.width || image.width <= 0) {
+ return c.json({
+ code: 500,
+ message: '图片宽度不能为空且必须大于 0!'
+ })
+ }
+ try {
+ await updateImage(image);
+ return c.json({ code: 200, message: '更新成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '更新失败!' })
+ }
+})
+
+app.put('/update-show', async (c) => {
+ const image = await c.req.json()
+ const data = await updateImageShow(image.id, image.show);
+ return c.json(data)
+})
+
+app.put('/update-tag', async (c) => {
+ const image = await c.req.json()
+ try {
+ await updateImageTag(image.imageId, image.tagId);
+ return c.json({
+ code: 200,
+ message: '更新成功!'
+ })
+ } catch (e) {
+ console.log(e)
+ return c.json({
+ code: 500,
+ message: '更新失败!'
+ })
+ }
+})
+
+export default app
\ No newline at end of file
diff --git a/hono/index.ts b/hono/index.ts
new file mode 100644
index 0000000..f063bcf
--- /dev/null
+++ b/hono/index.ts
@@ -0,0 +1,23 @@
+import 'server-only'
+import { Hono } from 'hono'
+import settings from '~/hono/settings'
+import hello from '~/hono/hello'
+import auth from '~/hono/auth'
+import copyright from '~/hono/copyright'
+import file from '~/hono/file'
+import image from '~/hono/image'
+import tags from '~/hono/tags'
+import alist from '~/hono/storage/alist'
+
+const route = new Hono()
+
+route.route('/settings', settings)
+route.route('/hello', hello)
+route.route('/auth', auth)
+route.route('/copyright', copyright)
+route.route('/file', file)
+route.route('/image', image)
+route.route('/tags', tags)
+route.route('/storage/alist', alist)
+
+export default route
\ No newline at end of file
diff --git a/hono/open/open.ts b/hono/open/open.ts
new file mode 100644
index 0000000..b4bfeca
--- /dev/null
+++ b/hono/open/open.ts
@@ -0,0 +1,43 @@
+import 'server-only'
+import { Hono } from 'hono'
+import { fetchImageByIdAndAuth, queryAuthStatus } from '~/server/db/query'
+
+const app = new Hono()
+
+app.get('/get-auth-status', async (c) => {
+ try {
+ const data = await queryAuthStatus();
+
+ return c.json({
+ code: 200,
+ message: '获取双因素状态成功!',
+ data: {
+ auth_enable: data?.config_value
+ }
+ })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '获取双因素状态失败!' })
+ }
+})
+
+app.get('/get-image-blob', async (c) => {
+ const { searchParams } = new URL(c.req.url)
+ const imageUrl = searchParams.get('imageUrl')
+ // @ts-ignore
+ const blob = await fetch(imageUrl).then(res => res.blob())
+ return new Response(blob)
+})
+
+app.get('/get-image-by-id', async (c) => {
+ const { searchParams } = new URL(c.req.url)
+ const id = searchParams.get('id')
+ const data = await fetchImageByIdAndAuth(Number(id));
+ if (data && data?.length > 0) {
+ return c.json({ code: 200, message: '图片数据获取成功!', data: data })
+ } else {
+ return c.json({ code: 500, message: '图片不存在或未公开展示!' })
+ }
+})
+
+export default app
diff --git a/hono/settings.ts b/hono/settings.ts
new file mode 100644
index 0000000..2a4f115
--- /dev/null
+++ b/hono/settings.ts
@@ -0,0 +1,109 @@
+import 'server-only'
+import { fetchCustomTitle, fetchR2Info, fetchS3Info, fetchSecretKey, fetchUserById } from '~/server/db/query'
+import { Config } from '~/types'
+import { updateAListConfig, updateCustomTitle, updatePassword, updateR2Config, updateS3Config } from '~/server/db/operate'
+import { auth } from '~/server/auth'
+import CryptoJS from 'crypto-js'
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/get-custom-title', async (c) => {
+ const data = await fetchCustomTitle();
+ return c.json(data)
+})
+
+app.get('/r2-info', async (c) => {
+ const data = await fetchR2Info();
+ return c.json(data)
+})
+
+app.get('/s3-info', async (c) => {
+ const data = await fetchS3Info();
+ return c.json(data)
+})
+
+app.put('/update-alist-info', async (c) => {
+ const query = await c.req.json()
+
+ const alistUrl = query?.find((item: Config) => item.config_key === 'alist_url').config_value
+ const alistToken = query?.find((item: Config) => item.config_key === 'alist_token').config_value
+
+ const data = await updateAListConfig({ alistUrl, alistToken });
+ return c.json(data)
+})
+
+app.put('/update-r2-info', async (c) => {
+ const query = await c.req.json()
+
+ const r2AccesskeyId = query?.find((item: Config) => item.config_key === 'r2_accesskey_id').config_value
+ const r2AccesskeySecret = query?.find((item: Config) => item.config_key === 'r2_accesskey_secret').config_value
+ const r2Endpoint = query?.find((item: Config) => item.config_key === 'r2_endpoint').config_value
+ const r2Bucket = query?.find((item: Config) => item.config_key === 'r2_bucket').config_value
+ const r2StorageFolder = query?.find((item: Config) => item.config_key === 'r2_storage_folder').config_value
+ const r2PublicDomain = query?.find((item: Config) => item.config_key === 'r2_public_domain').config_value
+
+ const data = await updateR2Config({ r2AccesskeyId, r2AccesskeySecret, r2Endpoint, r2Bucket, r2StorageFolder, r2PublicDomain });
+ return c.json(data)
+})
+
+app.put('/update-s3-info', async (c) => {
+ const query = await c.req.json()
+
+ const accesskeyId = query?.find((item: Config) => item.config_key === 'accesskey_id').config_value
+ const accesskeySecret = query?.find((item: Config) => item.config_key === 'accesskey_secret').config_value
+ const region = query?.find((item: Config) => item.config_key === 'region').config_value
+ const endpoint = query?.find((item: Config) => item.config_key === 'endpoint').config_value
+ const bucket = query?.find((item: Config) => item.config_key === 'bucket').config_value
+ const storageFolder = query?.find((item: Config) => item.config_key === 'storage_folder').config_value
+ const forcePathStyle = query?.find((item: Config) => item.config_key === 'force_path_style').config_value
+ const s3Cdn = query?.find((item: Config) => item.config_key === 's3_cdn').config_value
+ const s3CdnUrl = query?.find((item: Config) => item.config_key === 's3_cdn_url').config_value
+
+ const data = await updateS3Config({ accesskeyId, accesskeySecret, region, endpoint, bucket, storageFolder, forcePathStyle, s3Cdn, s3CdnUrl });
+ return c.json(data)
+})
+
+app.put('/update-custom-title', async (c) => {
+ const query = await c.req.json()
+ const data = await updateCustomTitle(query.title);
+ return c.json(data)
+})
+
+app.put('/update-password', async (c) => {
+ const { user } = await auth()
+ const pwd = await c.req.json()
+ const daUser = await fetchUserById(user?.id)
+ const secretKey = await fetchSecretKey()
+ if (!secretKey || !secretKey.config_value) {
+ return Response.json({
+ code: 500,
+ message: '更新失败!'
+ })
+ }
+ const hashedOldPassword = CryptoJS.HmacSHA512(pwd.oldPassword, secretKey?.config_value).toString()
+
+ try {
+ if (daUser && hashedOldPassword === daUser.password) {
+ const hashedNewPassword = CryptoJS.HmacSHA512(pwd.newPassword, secretKey?.config_value).toString()
+ await updatePassword(user?.id, hashedNewPassword);
+ return c.json({
+ code: 200,
+ message: '更新成功!'
+ })
+ } else {
+ return c.json({
+ code: 500,
+ message: '旧密码不匹配!'
+ })
+ }
+ } catch (e) {
+ console.log(e)
+ return c.json({
+ code: 500,
+ message: '更新失败!'
+ })
+ }
+})
+
+export default app
\ No newline at end of file
diff --git a/app/api/v1/alist-storages/route.ts b/hono/storage/alist.ts
similarity index 62%
rename from app/api/v1/alist-storages/route.ts
rename to hono/storage/alist.ts
index e68309a..a206e92 100644
--- a/app/api/v1/alist-storages/route.ts
+++ b/hono/storage/alist.ts
@@ -1,7 +1,16 @@
import 'server-only'
-import { fetchAListInfo } from '~/server/lib/query'
+import { fetchAListInfo } from '~/server/db/query'
-export async function GET() {
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/info', async (c) => {
+ const data = await fetchAListInfo();
+ return c.json(data)
+})
+
+app.get('/storages', async (c) => {
const findConfig = await fetchAListInfo()
const alistToken = findConfig.find((item: any) => item.config_key === 'alist_token')?.config_value || '';
const alistUrl = findConfig.find((item: any) => item.config_key === 'alist_url')?.config_value || '';
@@ -12,8 +21,7 @@ export async function GET() {
'Authorization': alistToken.toString(),
},
}).then(res => res.json())
+ return c.json(data)
+})
- return Response.json(data)
-}
-
-export const dynamic = 'force-dynamic'
\ No newline at end of file
+export default app
\ No newline at end of file
diff --git a/hono/tags.ts b/hono/tags.ts
new file mode 100644
index 0000000..ec30d63
--- /dev/null
+++ b/hono/tags.ts
@@ -0,0 +1,59 @@
+import 'server-only'
+import { fetchTagsListAndNotDefault } from '~/server/db/query'
+import { deleteTag, insertTag, updateTag, updateTagShow } from '~/server/db/operate'
+import { Hono } from 'hono'
+
+const app = new Hono()
+
+app.get('/get', async (c) => {
+ const data = await fetchTagsListAndNotDefault();
+ return c.json(data)
+})
+
+app.post('/add', async (c) => {
+ const tag = await c.req.json()
+ if (tag.tag_value && tag.tag_value.charAt(0) !== '/') {
+ return c.json({
+ code: 500,
+ message: '路由必须以 / 开头!'
+ })
+ }
+ try {
+ await insertTag(tag);
+ return c.json({ code: 200, message: '新增成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '新增失败!' })
+ }
+})
+
+app.put('/update', async (c) => {
+ const tag = await c.req.json()
+ if (tag.tag_value && tag.tag_value.charAt(0) !== '/') {
+ return c.json({
+ code: 500,
+ message: '路由必须以 / 开头!'
+ })
+ }
+ try {
+ await updateTag(tag);
+ return c.json({ code: 200, message: '更新成功!' })
+ } catch (e) {
+ console.log(e)
+ return c.json({ code: 500, message: '更新失败!' })
+ }
+})
+
+app.delete('/delete/:id', async (c) => {
+ const { id } = c.req.param()
+ const data = await deleteTag(Number(id));
+ return c.json(data)
+})
+
+app.put('/update-show', async (c) => {
+ const tag = await c.req.json()
+ const data = await updateTagShow(tag.id, tag.show);
+ return c.json(data)
+})
+
+export default app
\ No newline at end of file
diff --git a/next.config.mjs b/next.config.mjs
index 08e83d3..4cfd0a7 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
+ reactStrictMode: true,
compiler: {
removeConsole: process.env.NODE_ENV === "production",
},
diff --git a/package.json b/package.json
index 8ef58c9..4c6d05f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "picimpact",
- "version": "1.0.0",
+ "version": "1.1.0",
"private": true,
"author": "Bess Croft ",
"packageManager": "pnpm@9.7.1",
@@ -22,8 +22,10 @@
"@ant-design/nextjs-registry": "^1.0.1",
"@auth/prisma-adapter": "^2.5.3",
"@aws-sdk/client-s3": "^3.658.1",
+ "@hono/node-server": "^1.13.1",
"@nextui-org/react": "^2.4.8",
"@prisma/client": "5.17.0",
+ "@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
@@ -41,6 +43,7 @@
"dayjs": "^1.11.13",
"exifreader": "^4.23.5",
"framer-motion": "^11.9.0",
+ "hono": "^4.6.3",
"input-otp": "^1.2.4",
"lucide-react": "^0.446.0",
"next": "^14.2.13",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db7bf14..a85b3ad 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,12 +17,18 @@ importers:
'@aws-sdk/client-s3':
specifier: ^3.658.1
version: 3.658.1
+ '@hono/node-server':
+ specifier: ^1.13.1
+ version: 1.13.1(hono@4.6.3)
'@nextui-org/react':
specifier: ^2.4.8
version: 2.4.8(@types/react@18.3.10)(framer-motion@11.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.13)
'@prisma/client':
specifier: 5.17.0
version: 5.17.0(prisma@5.17.0)
+ '@radix-ui/react-avatar':
+ specifier: ^1.1.1
+ version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dialog':
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -74,6 +80,9 @@ importers:
framer-motion:
specifier: ^11.9.0
version: 11.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ hono:
+ specifier: ^4.6.3
+ version: 4.6.3
input-otp:
specifier: ^1.2.4
version: 1.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -475,6 +484,12 @@ packages:
'@formatjs/intl-localematcher@0.5.4':
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
+ '@hono/node-server@1.13.1':
+ resolution: {integrity: sha512-TSxE6cT5RHnawbjnveexVN7H2Dpn1YaLxQrCOLCUwD+hFbqbFsnJBgdWcYtASqtWVjA+Qgi8uqFug39GsHjo5A==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
'@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -1254,6 +1269,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-avatar@1.1.1':
+ resolution: {integrity: sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-collection@1.1.0':
resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
peerDependencies:
@@ -1303,6 +1331,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-context@1.1.1':
+ resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-dialog@1.0.5':
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
peerDependencies:
@@ -3351,6 +3388,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hono@4.6.3:
+ resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==}
+ engines: {node: '>=16.9.0'}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -5382,6 +5423,10 @@ snapshots:
dependencies:
tslib: 2.6.3
+ '@hono/node-server@1.13.1(hono@4.6.3)':
+ dependencies:
+ hono: 4.6.3
+
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -6569,6 +6614,18 @@ snapshots:
'@types/react': 18.3.10
'@types/react-dom': 18.3.0
+ '@radix-ui/react-avatar@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.10)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.10)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.10)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.10
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.10)(react@18.3.1)
@@ -6607,6 +6664,12 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.10
+ '@radix-ui/react-context@1.1.1(@types/react@18.3.10)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.10
+
'@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
@@ -9463,6 +9526,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hono@4.6.3: {}
+
ignore@5.3.2: {}
import-fresh@3.3.0:
diff --git a/public/fufu.jpg b/public/fufu.jpg
deleted file mode 100644
index a02045c..0000000
Binary files a/public/fufu.jpg and /dev/null differ
diff --git a/server/lib/actions.ts b/server/actions.ts
similarity index 95%
rename from server/lib/actions.ts
rename to server/actions.ts
index 735e170..30e57be 100644
--- a/server/lib/actions.ts
+++ b/server/actions.ts
@@ -1,7 +1,7 @@
'use server'
import { signIn, signOut } from '~/server/auth'
-import { queryAuthSecret, queryAuthStatus } from '~/server/lib/query'
+import { queryAuthSecret, queryAuthStatus } from '~/server/db/query'
import * as OTPAuth from "otpauth"
export async function authenticate(
diff --git a/server/auth.ts b/server/auth.ts
index ba0e364..c5815e3 100644
--- a/server/auth.ts
+++ b/server/auth.ts
@@ -5,7 +5,7 @@ import CredentialsProvider from 'next-auth/providers/credentials'
import { db } from '~/server/lib/db'
import { z } from 'zod'
import CryptoJS from 'crypto-js'
-import { fetchSecretKey } from '~/server/lib/query'
+import { fetchSecretKey } from '~/server/db/query'
const prisma = new PrismaClient()
diff --git a/server/lib/operate.ts b/server/db/operate.ts
similarity index 99%
rename from server/lib/operate.ts
rename to server/db/operate.ts
index be9985d..a2080c9 100644
--- a/server/lib/operate.ts
+++ b/server/db/operate.ts
@@ -2,7 +2,7 @@
import { db } from '~/server/lib/db'
import { TagType, ImageType, CopyrightType } from '~/types'
-import {queryAuthTemplateSecret} from "~/server/lib/query";
+import {queryAuthTemplateSecret} from "~/server/db/query";
export async function insertTag(tag: TagType) {
if (!tag.sort || tag.sort < 0) {
diff --git a/server/lib/query.ts b/server/db/query.ts
similarity index 92%
rename from server/lib/query.ts
rename to server/db/query.ts
index 00aeaec..d772846 100644
--- a/server/lib/query.ts
+++ b/server/db/query.ts
@@ -133,7 +133,21 @@ export async function fetchServerImagesListByTag(pageNum: number, tag: string) {
SELECT
image.*,
STRING_AGG(tags."name", ',') AS tag_names,
- STRING_AGG(tags.id::text, ',') AS tag_values
+ STRING_AGG(tags.id::text, ',') AS tag_values,
+ (
+ SELECT json_agg(row_to_json(t))
+ FROM (
+ SELECT copyright.id
+ FROM "public"."Copyright" AS copyright
+ INNER JOIN "public"."ImageCopyrightRelation" AS icrelation
+ ON copyright.id = icrelation."copyrightId"
+ INNER JOIN "public"."Images" AS image_child
+ ON icrelation."imageId" = image_child."id"
+ WHERE copyright.del = 0
+ AND image_child.del = 0
+ AND image.id = image_child.id
+ ) t
+ ) AS copyrights
FROM
"public"."Images" AS image
INNER JOIN "public"."ImageTagRelation" AS relation
@@ -150,6 +164,16 @@ export async function fetchServerImagesListByTag(pageNum: number, tag: string) {
ORDER BY image.sort DESC, image.create_time DESC, image.update_time DESC
LIMIT 8 OFFSET ${(pageNum - 1) * 8}
`
+
+ if (findAll) {
+ // @ts-ignore
+ findAll?.map((item: any) => {
+ if (item.copyrights) {
+ item.copyrights = item?.copyrights.map((item: any) => Number(item.id))
+ }
+ })
+ }
+
return findAll;
}
const findAll = await db.$queryRaw`
@@ -173,14 +197,12 @@ export async function fetchServerImagesListByTag(pageNum: number, tag: string) {
) AS copyrights
FROM
"public"."Images" AS image
- INNER JOIN "public"."ImageTagRelation" AS relation
+ LEFT JOIN "public"."ImageTagRelation" AS relation
ON image.id = relation."imageId"
- INNER JOIN "public"."Tags" AS tags
+ LEFT JOIN "public"."Tags" AS tags
ON relation.tag_value = tags.tag_value
WHERE
image.del = 0
- AND
- tags.del = 0
GROUP BY image.id
ORDER BY image.sort DESC, image.create_time DESC, image.update_time DESC
LIMIT 8 OFFSET ${(pageNum - 1) * 8}
@@ -230,14 +252,12 @@ export async function fetchServerImagesPageTotalByTag(tag: string) {
image.id
FROM
"public"."Images" AS image
- INNER JOIN "public"."ImageTagRelation" AS relation
+ LEFT JOIN "public"."ImageTagRelation" AS relation
ON image.id = relation."imageId"
- INNER JOIN "public"."Tags" AS tags
+ LEFT JOIN "public"."Tags" AS tags
ON relation.tag_value = tags.tag_value
WHERE
image.del = 0
- AND
- tags.del = 0
) AS unique_images;
`
// @ts-ignore
diff --git a/server/lib/user.ts b/server/user.ts
similarity index 100%
rename from server/lib/user.ts
rename to server/user.ts
diff --git a/style/globals.css b/style/globals.css
index 7c4e738..e6f5358 100644
--- a/style/globals.css
+++ b/style/globals.css
@@ -16,6 +16,11 @@
}
}
+[data-theme='dark'] {
+ --background: black;
+ --foreground: white;
+}
+
.semi-tagInput {
background-color: #e5e7eb !important;
}
@@ -63,6 +68,16 @@
--ring: 240 10% 3.9%;
--radius: 0.5rem;
+
+ --chart-1: 12 76% 61%;
+
+ --chart-2: 173 58% 39%;
+
+ --chart-3: 197 37% 24%;
+
+ --chart-4: 43 74% 66%;
+
+ --chart-5: 27 87% 67%;
}
.dark {
@@ -93,6 +108,11 @@
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
}
}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 5365998..b58698b 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -10,74 +10,93 @@ const config = {
],
prefix: "",
theme: {
- container: {
- center: true,
- padding: "2rem",
- screens: {
- "2xl": "1400px",
- },
- },
- extend: {
- colors: {
- border: "hsl(var(--border))",
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
- background: "hsl(var(--background))",
- foreground: "hsl(var(--foreground))",
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- destructive: {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- popover: {
- DEFAULT: "hsl(var(--popover))",
- foreground: "hsl(var(--popover-foreground))",
- },
- card: {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
- },
- },
- borderRadius: {
- lg: "var(--radius)",
- md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)",
- },
- keyframes: {
- "accordion-down": {
- from: { height: "0" },
- to: { height: "var(--radix-accordion-content-height)" },
- },
- "accordion-up": {
- from: { height: "var(--radix-accordion-content-height)" },
- to: { height: "0" },
- },
- "trail": {
- "0%": { "--angle": "0deg" },
- "100%": { "--angle": "360deg" },
- },
- },
- animation: {
- "accordion-down": "accordion-down 0.2s ease-out",
- "accordion-up": "accordion-up 0.2s ease-out",
- "trail": "trail var(--duration) linear infinite",
- },
- },
+ container: {
+ center: 'true',
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px'
+ }
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ }
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0'
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)'
+ }
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)'
+ },
+ to: {
+ height: '0'
+ }
+ },
+ 'trail': {
+ '0%': {
+ '--angle': '0deg'
+ },
+ '100%': {
+ '--angle': '360deg'
+ }
+ }
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ 'trail': 'trail var(--duration) linear infinite'
+ }
+ }
},
plugins: [nextui(), require('tailwindcss-animate'), require('tailwind-scrollbar-hide')],
} satisfies Config
diff --git a/types/http.ts b/types/http.ts
new file mode 100644
index 0000000..c9ff752
--- /dev/null
+++ b/types/http.ts
@@ -0,0 +1,5 @@
+export type Response = {
+ code: number,
+ message: string,
+ data?: any
+}
\ No newline at end of file