From d43e33380a796d4b21af603689ffb1fbc540f3fc Mon Sep 17 00:00:00 2001 From: besscroft Date: Thu, 10 Oct 2024 23:09:04 +0800 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20=E5=BC=95=E5=85=A5=20hono.js?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +- .env.example | 6 + .github/workflows/build-next.yaml | 36 ++++++ .gitignore | 2 +- Dockerfile | 1 + api/auth.ts | 74 +++++++++++ api/copyright.ts | 59 +++++++++ .../v1/file-upload/route.ts => api/file.ts | 52 ++++---- api/hello.ts | 11 ++ api/image.ts | 117 ++++++++++++++++++ api/index.ts | 23 ++++ api/open/open.ts | 43 +++++++ api/settings.ts | 109 ++++++++++++++++ .../route.ts => api/storage/alist.ts | 20 ++- api/tags.ts | 59 +++++++++ app/(default)/[...tag]/page.tsx | 2 +- app/(default)/label/[...tag]/page.tsx | 2 +- app/(default)/page.tsx | 2 +- app/admin/copyright/page.tsx | 2 +- app/admin/list/page.tsx | 2 +- app/admin/page.tsx | 2 +- app/admin/settings/authenticator/page.tsx | 6 +- app/admin/settings/password/page.tsx | 2 +- app/admin/settings/preferences/page.tsx | 6 +- app/admin/tag/page.tsx | 2 +- app/api/[[...route]]/route.ts | 22 ++++ app/api/open/get-auth-status/route.ts | 21 ---- app/api/open/get-image-blob/route.ts | 12 -- app/api/open/get-image-by-id/route.ts | 16 --- app/api/route.ts | 5 - app/api/v1/alist-info/route.ts | 9 -- app/api/v1/auth-login-validate/route.ts | 28 ----- app/api/v1/auth-validate/route.ts | 31 ----- app/api/v1/copyright-add/route.ts | 14 --- app/api/v1/copyright-delete/[id]/route.ts | 11 -- app/api/v1/copyright-update/route.ts | 14 --- app/api/v1/get-copyrights/route.ts | 9 -- app/api/v1/get-custom-title/route.ts | 9 -- app/api/v1/get-seed-secret/route.ts | 34 ----- app/api/v1/get-tags/route.ts | 9 -- app/api/v1/image-add/route.ts | 32 ----- app/api/v1/image-batch-delete/route.ts | 14 --- app/api/v1/image-delete/[id]/route.ts | 16 --- app/api/v1/image-update/route.ts | 32 ----- app/api/v1/r2-info/route.ts | 9 -- app/api/v1/remove-auth/route.ts | 13 -- app/api/v1/s3-info/route.ts | 9 -- app/api/v1/tag-add/route.ts | 20 --- app/api/v1/tag-delete/[id]/route.ts | 11 -- app/api/v1/tag-update/route.ts | 20 --- app/api/v1/update-alist-info/route.ts | 14 --- app/api/v1/update-copyright-default/route.ts | 9 -- app/api/v1/update-copyright-show/route.ts | 9 -- app/api/v1/update-custom-title/route.ts | 9 -- app/api/v1/update-image-show/route.ts | 9 -- app/api/v1/update-image-tag/route.ts | 20 --- app/api/v1/update-password/route.ts | 42 ------- app/api/v1/update-r2-info/route.ts | 18 --- app/api/v1/update-s3-info/route.ts | 21 ---- app/api/v1/update-tag-show/route.ts | 9 -- app/layout.tsx | 2 +- .../admin/copyright/CopyrightAddSheet.tsx | 2 +- .../admin/copyright/CopyrightEditSheet.tsx | 2 +- components/admin/copyright/CopyrightList.tsx | 6 +- .../admin/list/ImageBatchDeleteSheet.tsx | 2 +- components/admin/list/ImageEditSheet.tsx | 4 +- components/admin/list/ImageView.tsx | 2 +- components/admin/list/ListProps.tsx | 8 +- .../settings/storages/AListEditSheet.tsx | 4 +- .../admin/settings/storages/AListTabs.tsx | 2 +- .../admin/settings/storages/R2EditSheet.tsx | 4 +- components/admin/settings/storages/R2Tabs.tsx | 2 +- .../admin/settings/storages/S3EditSheet.tsx | 4 +- components/admin/settings/storages/S3Tabs.tsx | 2 +- components/admin/tag/TagAddSheet.tsx | 2 +- components/admin/tag/TagEditSheet.tsx | 2 +- components/admin/tag/TagList.tsx | 4 +- components/admin/upload/FileUpload.tsx | 10 +- components/layout/DropMenu.tsx | 2 +- components/layout/DynamicNavbar.tsx | 2 +- components/layout/Header.tsx | 2 +- components/layout/VaulDrawer.tsx | 2 +- components/login/UserFrom.tsx | 2 +- next.config.mjs | 1 + package.json | 2 + pnpm-lock.yaml | 22 ++++ server/{lib => }/actions.ts | 2 +- server/auth.ts | 2 +- server/{lib => db}/operate.ts | 2 +- server/{lib => db}/query.ts | 0 server/{lib => }/user.ts | 0 types/http.ts | 5 + 92 files changed, 693 insertions(+), 641 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/build-next.yaml create mode 100644 api/auth.ts create mode 100644 api/copyright.ts rename app/api/v1/file-upload/route.ts => api/file.ts (79%) create mode 100644 api/hello.ts create mode 100644 api/image.ts create mode 100644 api/index.ts create mode 100644 api/open/open.ts create mode 100644 api/settings.ts rename app/api/v1/alist-storages/route.ts => api/storage/alist.ts (62%) create mode 100644 api/tags.ts create mode 100644 app/api/[[...route]]/route.ts delete mode 100644 app/api/open/get-auth-status/route.ts delete mode 100644 app/api/open/get-image-blob/route.ts delete mode 100644 app/api/open/get-image-by-id/route.ts delete mode 100644 app/api/route.ts delete mode 100644 app/api/v1/alist-info/route.ts delete mode 100644 app/api/v1/auth-login-validate/route.ts delete mode 100644 app/api/v1/auth-validate/route.ts delete mode 100644 app/api/v1/copyright-add/route.ts delete mode 100644 app/api/v1/copyright-delete/[id]/route.ts delete mode 100644 app/api/v1/copyright-update/route.ts delete mode 100644 app/api/v1/get-copyrights/route.ts delete mode 100644 app/api/v1/get-custom-title/route.ts delete mode 100644 app/api/v1/get-seed-secret/route.ts delete mode 100644 app/api/v1/get-tags/route.ts delete mode 100644 app/api/v1/image-add/route.ts delete mode 100644 app/api/v1/image-batch-delete/route.ts delete mode 100644 app/api/v1/image-delete/[id]/route.ts delete mode 100644 app/api/v1/image-update/route.ts delete mode 100644 app/api/v1/r2-info/route.ts delete mode 100644 app/api/v1/remove-auth/route.ts delete mode 100644 app/api/v1/s3-info/route.ts delete mode 100644 app/api/v1/tag-add/route.ts delete mode 100644 app/api/v1/tag-delete/[id]/route.ts delete mode 100644 app/api/v1/tag-update/route.ts delete mode 100644 app/api/v1/update-alist-info/route.ts delete mode 100644 app/api/v1/update-copyright-default/route.ts delete mode 100644 app/api/v1/update-copyright-show/route.ts delete mode 100644 app/api/v1/update-custom-title/route.ts delete mode 100644 app/api/v1/update-image-show/route.ts delete mode 100644 app/api/v1/update-image-tag/route.ts delete mode 100644 app/api/v1/update-password/route.ts delete mode 100644 app/api/v1/update-r2-info/route.ts delete mode 100644 app/api/v1/update-s3-info/route.ts delete mode 100644 app/api/v1/update-tag-show/route.ts rename server/{lib => }/actions.ts (95%) rename server/{lib => db}/operate.ts (99%) rename server/{lib => db}/query.ts (100%) rename server/{lib => }/user.ts (100%) create mode 100644 types/http.ts diff --git a/.env b/.env index dadb0b7..50eb5cb 100644 --- a/.env +++ b/.env @@ -1,4 +1,6 @@ # 数据库 url -DATABASE_URL="postgres://postgres:666666@localhost:5432/postgres" +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/.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/.github/workflows/build-next.yaml b/.github/workflows/build-next.yaml new file mode 100644 index 0000000..41e37dd --- /dev/null +++ b/.github/workflows/build-next.yaml @@ -0,0 +1,36 @@ +name: Dev Docker Multi-arch Image CI & CD + +on: + push: + branches: + - next + +jobs: + build: + name: Running Compile Next Multi-arch Docker Image + runs-on: ubuntu-latest + steps: + - name: Checkout PicImpact + uses: actions/checkout@v4 + - name: Get Version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + id: set_up_buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push next + id: docker_build + uses: docker/build-push-action@v5 + with: + context: ./ + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/picimpact:next 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/api/auth.ts b/api/auth.ts new file mode 100644 index 0000000..5fdd45f --- /dev/null +++ b/api/auth.ts @@ -0,0 +1,74 @@ +import 'server-only' +import { queryAuthSecret, 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/api/copyright.ts b/api/copyright.ts new file mode 100644 index 0000000..458dafc --- /dev/null +++ b/api/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/api/file.ts similarity index 79% rename from app/api/v1/file-upload/route.ts rename to api/file.ts index 71016c9..caece4f 100644 --- a/app/api/v1/file-upload/route.ts +++ b/api/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.get('/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/api/hello.ts b/api/hello.ts new file mode 100644 index 0000000..a56b2ae --- /dev/null +++ b/api/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/api/image.ts b/api/image.ts new file mode 100644 index 0000000..f9d87a1 --- /dev/null +++ b/api/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('/image-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/api/index.ts b/api/index.ts new file mode 100644 index 0000000..6b5627f --- /dev/null +++ b/api/index.ts @@ -0,0 +1,23 @@ +import 'server-only' +import { Hono } from 'hono' +import settings from '~/api/settings' +import hello from '~/api/hello' +import auth from '~/api/auth' +import copyright from '~/api/copyright' +import file from '~/api/file' +import image from '~/api/image' +import tags from '~/api/tags' +import alist from '~/api/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/api/open/open.ts b/api/open/open.ts new file mode 100644 index 0000000..b4bfeca --- /dev/null +++ b/api/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/api/settings.ts b/api/settings.ts new file mode 100644 index 0000000..2a4f115 --- /dev/null +++ b/api/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/api/storage/alist.ts similarity index 62% rename from app/api/v1/alist-storages/route.ts rename to api/storage/alist.ts index e68309a..a206e92 100644 --- a/app/api/v1/alist-storages/route.ts +++ b/api/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/api/tags.ts b/api/tags.ts new file mode 100644 index 0000000..ec30d63 --- /dev/null +++ b/api/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/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/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..7dba263 --- /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 '~/api' +import open from '~/api/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..1aad4c2 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 } 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..8ce672f 100644 --- a/components/layout/DropMenu.tsx +++ b/components/layout/DropMenu.tsx @@ -3,7 +3,7 @@ import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Avatar } 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' 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..38adc30 100644 --- a/components/layout/VaulDrawer.tsx +++ b/components/layout/VaulDrawer.tsx @@ -21,7 +21,7 @@ import { Copyright, Info } from 'lucide-react' -import { loginOut } from '~/server/lib/actions' +import { loginOut } from '~/server/actions' export default function VaulDrawer() { const { data: session, status } = useSession() 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/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..60b1b57 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@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-dialog": "^1.1.1", @@ -41,6 +42,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..2b42d66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ 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) @@ -74,6 +77,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 +481,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'} @@ -3351,6 +3363,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 +5398,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 @@ -9463,6 +9483,8 @@ snapshots: dependencies: function-bind: 1.1.2 + hono@4.6.3: {} + ignore@5.3.2: {} import-fresh@3.3.0: 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 100% rename from server/lib/query.ts rename to server/db/query.ts 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/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 From 06601b1225afc5e3edd70921f291c8f57d6b5cfc Mon Sep 17 00:00:00 2001 From: besscroft Date: Thu, 10 Oct 2024 23:57:47 +0800 Subject: [PATCH 02/11] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E6=89=93?= =?UTF-8?q?=E5=8C=85=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-next.yaml | 2 +- app/api/[[...route]]/route.ts | 4 ++-- {api => hono}/auth.ts | 2 +- {api => hono}/copyright.ts | 0 {api => hono}/file.ts | 0 {api => hono}/hello.ts | 0 {api => hono}/image.ts | 0 {api => hono}/index.ts | 16 ++++++++-------- {api => hono}/open/open.ts | 0 {api => hono}/settings.ts | 0 {api => hono}/storage/alist.ts | 0 {api => hono}/tags.ts | 0 12 files changed, 12 insertions(+), 12 deletions(-) rename {api => hono}/auth.ts (95%) rename {api => hono}/copyright.ts (100%) rename {api => hono}/file.ts (100%) rename {api => hono}/hello.ts (100%) rename {api => hono}/image.ts (100%) rename {api => hono}/index.ts (55%) rename {api => hono}/open/open.ts (100%) rename {api => hono}/settings.ts (100%) rename {api => hono}/storage/alist.ts (100%) rename {api => hono}/tags.ts (100%) diff --git a/.github/workflows/build-next.yaml b/.github/workflows/build-next.yaml index 41e37dd..2faab8b 100644 --- a/.github/workflows/build-next.yaml +++ b/.github/workflows/build-next.yaml @@ -33,4 +33,4 @@ jobs: file: ./Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/picimpact:next + tags: ${{ secrets.DOCKERHUB_USERNAME }}/picimpact:dev diff --git a/app/api/[[...route]]/route.ts b/app/api/[[...route]]/route.ts index 7dba263..52dc9c0 100644 --- a/app/api/[[...route]]/route.ts +++ b/app/api/[[...route]]/route.ts @@ -1,8 +1,8 @@ import 'server-only' import { handle } from 'hono/vercel' import { Hono } from 'hono' -import route from '~/api' -import open from '~/api/open/open' +import route from '~/hono' +import open from '~/hono/open/open' const app = new Hono().basePath('/api') diff --git a/api/auth.ts b/hono/auth.ts similarity index 95% rename from api/auth.ts rename to hono/auth.ts index 5fdd45f..d4105e3 100644 --- a/api/auth.ts +++ b/hono/auth.ts @@ -1,5 +1,5 @@ import 'server-only' -import { queryAuthSecret, queryAuthTemplateSecret } from '~/server/db/query' +import { queryAuthTemplateSecret } from '~/server/db/query' import * as OTPAuth from 'otpauth' import { deleteAuthSecret, saveAuthSecret, saveAuthTemplateSecret } from '~/server/db/operate' import { Hono } from 'hono' diff --git a/api/copyright.ts b/hono/copyright.ts similarity index 100% rename from api/copyright.ts rename to hono/copyright.ts diff --git a/api/file.ts b/hono/file.ts similarity index 100% rename from api/file.ts rename to hono/file.ts diff --git a/api/hello.ts b/hono/hello.ts similarity index 100% rename from api/hello.ts rename to hono/hello.ts diff --git a/api/image.ts b/hono/image.ts similarity index 100% rename from api/image.ts rename to hono/image.ts diff --git a/api/index.ts b/hono/index.ts similarity index 55% rename from api/index.ts rename to hono/index.ts index 6b5627f..f063bcf 100644 --- a/api/index.ts +++ b/hono/index.ts @@ -1,13 +1,13 @@ import 'server-only' import { Hono } from 'hono' -import settings from '~/api/settings' -import hello from '~/api/hello' -import auth from '~/api/auth' -import copyright from '~/api/copyright' -import file from '~/api/file' -import image from '~/api/image' -import tags from '~/api/tags' -import alist from '~/api/storage/alist' +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() diff --git a/api/open/open.ts b/hono/open/open.ts similarity index 100% rename from api/open/open.ts rename to hono/open/open.ts diff --git a/api/settings.ts b/hono/settings.ts similarity index 100% rename from api/settings.ts rename to hono/settings.ts diff --git a/api/storage/alist.ts b/hono/storage/alist.ts similarity index 100% rename from api/storage/alist.ts rename to hono/storage/alist.ts diff --git a/api/tags.ts b/hono/tags.ts similarity index 100% rename from api/tags.ts rename to hono/tags.ts From 3e574d63993de5c6cc52758365ac38b09d655d4e Mon Sep 17 00:00:00 2001 From: besscroft Date: Fri, 11 Oct 2024 16:13:58 +0800 Subject: [PATCH 03/11] =?UTF-8?q?pub:=20=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 6 ------ public/fufu.jpg | Bin 112430 -> 0 bytes 2 files changed, 6 deletions(-) delete mode 100644 .env delete mode 100644 public/fufu.jpg diff --git a/.env b/.env deleted file mode 100644 index 50eb5cb..0000000 --- a/.env +++ /dev/null @@ -1,6 +0,0 @@ -# 数据库 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/public/fufu.jpg b/public/fufu.jpg deleted file mode 100644 index a02045c4a78f94b9dd2e2ffe42f6bd9d6868a200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112430 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 m@OvemGBO1Jbk!2Le;(DcNfrd9H?f} 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

GdbZALQ7Sn-zl?5)0%!EuGPHyBj@yBGbh3 zbDTm~Pb9tLBnfV(^ZLhU$CvP*ENzUSkFf*Mj+;#L~mMW5Wf02Qn4*-A;T&XW&s|8Zk{4QOL{1IvP zr7@7fo908c6={p5Cuy#Wwt`fhK*JIDM~VHJuu43+?;#yxAw{KsW+{6E^h8tn@!yDk zoI63DAd%%J-q7|BZc{=tNB);^Q49;Mzp?AlKQ+iSgNxCX1TGk=o9)^_>bN`BarrlP2G%dZ6koWaU5`po|CX)q`k zUP#H>h}-5Kr459OBtsH!`)T9t`RwXBH@Bz07B;g9YWyAz^s4h3WnEb!@U&-AGKuj@ zTVzbPoQdG>V(O#CVg#rU6o|T*h9s>v@~|_Mu6La2l&gFG|wN~fYywyYq4uVmy(&^ zCY~X^EnE&eg?f)HPe%i;>%GsfA?04kt?tf_M$x$wQWCkuVa_Yj4!&^bjNrpBe}N?j zR9DK-jl$%StkcrlsgXy%Liwp1t`GO8>lbLg+i(#I9;v3Kl^1}+7@AZQ$J*xdUqqoDBz{?1woF{Z+FoXY4(UF zYZumP7D$i4G|Yad)`s~CB`G)lg+aWxW9(3@#*6r|4@+D;y)(IX&v&pWzB}CcB`3cu z{%UN@X(?GyRJak^$;${&!Y3ZO!>)HoT2kG4^Sm=8O-ZEqE6bwwCVo}&%TD~B8m35r zC<-g`WX*Hk>D-zKFGA0_k8fF-1)OJzZjN91VEh*Q!*HPIrM0R!p*UPwn-F9i5ZkWCDD%kC#7a*vNYD8nY@X0^du(GBZYpV-;_1M(1|z=y8nzWD zd)mEp@B`y;w!EzLX>LwMWUOjHn40_V>XDlmuhsP*wN^QgcaE4CnfEWk3$t{9OlMlJ z$3xfXm4~;p;!8SSX>n9iuWAl^oBCj4Z}N{7~Q7(M8>ccKi;~ z`Sgh^hw{V|e+jz!4eMF!oZCYg6@?M9Tgq^QZMB9)`kLRPcJ5Jlgt&i!AqN>_%cSI* zh`cA5LXB5A;Y)T#22^&q!Z_Wq9`E(&x{rk$hj}fsODVs7b<-mzC|~wK41QlPvGqzB zEUcw^op}b4Li0=3#J>oqOW)A{U~@Qn3T@0M+e6B1-_|k`%B!&XM3RZl{ThWTy{vji z*j-=WrBiavEI&WMt|e4;)z%6lgA0NaW;Mcoe?%>5AZgpq8Dr<xG^YcS)5?(y4kLgR z%X24-VZA7<%fvXM&F>mWfg5rhC-tO_?sx)_kId4dMu8My3OO6W)xx)G#;GPq^OL^5 z+H;Y(d&m0xN9VJR%$LHe5dwZ1Xg3NoD^ zaLx`@Mo8)@neHZLZ#0fc`PH0aJ7~ZhMr%nv)yB`dJ(I_s>p+xh1P}=C%Q8jg1wt{JM$$J06(h8+&fHk$t@Z?=pU9pg>yA}t8Mu~9x%{B!k%Wb>Cfyo? z*^lo?ucRkwWbne3wAa#hXbQr=)2$*TY7qt$Rx0-Y0B12mck_SjJIbu=6+tBOt5_Nq z+R5z*zyh>pf%46GJw#OUMGR%#l@Ui1xC+ube9ER9OseyfgBl#VRl`bUWplvp1BGbI zvte&2NWR)$E_ne{Pc-gcS1fAFM&4iVhCc^68*vdlX{;r9tN$hp}LE-i2Om~r=k0Sq4$&Q2OV5Jh}FBgPJKOyr5j!R#$o_3 zihw6Rf{kzX9JLXQZ{tT+D4k}v3L3bb)g*}!%diypGRwLlvcB=GMGo?E->oWKPvsj5 zd76UKD6ny~-Azex6j|DB{RbL*splnQ1T6(sY!9@PkQ@a=W=Rh@+B`z?706dgJZqI2 z#c#o{UR5Q*a#9dU>rvYU@kn-$=rtVoao#_{*`LE zV&5uJg;D4#<+i7g{{Rv8Y@xV{MJtiZy*1eK#k}?Z02QOYj?Hn2uX7xJ=i_Rcdkqx= zrUC=xi3VNj=IZkMB0^w;{ggY;^R*%*Y84bJ^d0>tKGTR)?p^Ki)eKKGixTl;PZ*Wb z(ml~e0g(RyYMq+;;cekKi8z{<5|y*Jz>RpCjwFc!Al>~=ts{G|3=y9f;ypXD#`2sZ zlS^Vj21gj^wPB&|ZhvDA#Aorh3bD~vwhAP`X#n3NRqmg*gDG!`r0e(vT;1H>KHTya z=s6F7pgHPgT;8VIC zq;RaHV2(K+qO^{H<28WD;$i;)6$QlBvhBu7u-rlZgjJRFH?i#|=N@$F?M|%y!-w2Q z+;gjTz6ZVmIU`=|_cQHxdjX2zGIvwnLYslwJp~!=Wby$T{5zT~%WksO&7^7&juJc-e6qZ3V!oe`x;z6ZZynMJ&exm+Lq42rZ*0JiocE7oecA zB3B|=`tnbOUFm|wqOrrb2Aug9;ndPZ6`Dq7d0WjRZ(~G^<0ClXN@bIIQyO~tkNBVf z7#-W2q=6d@3*&I2NhF=*>arhKwtJE?@8FD7?$(y;0upDNd!LOk`h}{Z5!%0))DLNC z9vW2JJKG!U{viqdoN~XxX|C>wJ)NN*tki6;8Hq8HNy#~{sNH*?t$j}G{i8z0;PFr? zEaFcvEPZKlxD&FY-4$$OAv)<3J6`t;B`y6V`5O)l5?t_92N>Q6AG2GyL+qLJoUNC%4 z1vS(IKWIKw*Xj6CjT@Vn@~WVx#~G$9dQ@TCbTItQEt@eNw7(^!ia3T50M^@BJ2=|L z<4I#0?~#j|RYe$80D51)=Y0(XH*H1mZ1|bvf6|vh zS%0}9`O(leM}tZ#O$TP7vv$k;#cF*IayP_Man>_Z!7MCRMJmIS3{gI`vB>8O8c${Aq*A9;YNzKz|ScC$pEn^Yf1tfRq|-e1sER@U$)%rK;5&{DZ4WxT`ntH>63 zB#Hcz3?DkI#p`B6 zgd7lX6=`lHCEv)usi4Z`_YIsJe5y-WSw>S~xs~NtR+1oYVNaMXh{(qJm`b=M*r!pu9~@9-UF#wF?*^ijNH9hiaUI<1&h8b<+nO4gc5%EM zjMnp=TO^F~?qr{3omlS3Sz~ryAxRV^n*{c)yVjap#9Wqab)hZc0ES{Hi^k&3`O$`B z=YQ6_7{>N`{{X^{0I~e(Qg-q~vO1mAEhKN+OY&Nd+63T(Jx>1Sb)ScLYu$tFv#%Zf z-+j8jj@*<3YD{qDU-4^?ayo-bMIj3+>>j*VyQQQtXxpMVl-VCTu#m@ggi02lxYT};>`p{W7J(BieaMEHu+ zw%BOyM?VAnM*~RaH&+CV9wMZ@(r_F+?skSjCm@4Y7gsk<9#gbcLXJQR+oQpc zoM=|=?r3*LyZ}zTV}2+x0F3d~zq_9A16%w1`;BKhmI?t=LJPHBp9Vc?ZSK$zFbBrI zr#oQ0qJ@fnDOl3lTd&~H#+6lksy;(d6~|TvtZvSu$64+k;MJdGYj^G|nY#!ilc3%4 zHEnO(mXbn9&*o`WRAE#B=qmC%aT#~Qw<0;!j+2Hsgi{t6J`;@Yq;z!*j2TY?4o1JE zlI&T+oZec|Fq!VXInh;Nf}oCsQMR||8jsSag(ZN-gdLIL!J-P14-xGqm_)w3t3&8^ zzi+JE#i|Q=rjaL{ z>}>wdCEOHuB#|Orr)&y?M%~^3(vxooPxTs+EHqpjwjqz1JX)`QlQAYcDE|QKYB5i6 z=1YkflhowZ*+|etgBjjx-quTlyK?h2W20TqXKfZwH$U9fmWP4`24J$uocb*^q@9!* zHMvi|^|Rv3<(TrVjGqzDLCUm_m5$Fbpbs}rOG>x8PEyX~j%<9Qk~=*g9@8)f-acbd zeR}tNDcC}P)PFJf3b)agkR7t7Sv~nTnz*@G6bx-S@+BRne$rB2L%4s16>n{@M3V1* zLz3&@-w~;XK?b8@K?jK3YCi+Aa^foL1f67s{F1Id2A(&HM~%iA7^@FaS~^HBtdQ}pNLar6AOJ7hwDA!j+ZO zGO%Sl$L=s|p0X8zQUMhdeV8f_hdaGHH!$-l;x&CJlkF_y=#15%BE%BO)vD&@E!V3a0;ai^W0qq|?z|X^ zXD__(J!lc^rIbXu=gPD`ihU(1o_vdSKlhr_`e7^pwYC7<5C8*&bQNrYxw>N|%kKi5 zvbxQ3Hs^2ILCJkhL2OaJv2@#W9v+^8hIpFijZyZuGMH^pNaM`M$e$BdnlcMdwv~jl z0lrt@zYdk8K9WNYQqTz>-#%J)yT6mQnnpjwf3-&vL_8zs&awtc;ZiY{RzC6FS|>uu zG!e9!jv_znYK|zNj3j6g1r{=74azv09xnuYhcieyC(XabDuv-%PfQgr1*!CDNAwkC zV)sY1azuIK&8hA-7+TrH9$tgWo~EsIQbzr~)AsZF`BzoV$bbb=wIg{~`=3hk<=mHc zJI^*f1zTHoe`zKRq46w#K57g$S z(2fQ|b86Z^-Gv8$JNI*k$t zPzMcI`JrAW@TqVpa zDs$pG8v0Jzzm0l8D?$(~0rfR=F`8Cm+V~9#sGjfTB#K@@$`qx_!hjH3@tatxvL-$TbA#8h4+DtT_Jn{TdJ zPjAnP`Y_>9O7Ke@tGT0ae=0`~s_VgkVe_f%iZPnh0O!pY1%L2qStpIWJC0SAxQ|0t zvmG09Adr)7Df>3}4y)TmytjCd9w>S>GQ4icz^kKRj$n$>x4&Gr3{To7=_rLfK&F8Y z4aS-mUE_UEWovQgh;4hrajEn)$&(8S2Sd53m63|Nl_Q8F2Ca0B#kLnaar=g?uA#b2 z#M{ReJV)+B-W}f#!0SVgusy-YRw$kw-g>y|&i?>lD4M~dHRoP^nKg~t_)*?->`uJu z-;DCC_qfpg#c^#7%7G&992jvFbUDw4agR|~)>2xqB!))d!mg!RZJ?Np>^94aU-(v# zk76?zINS$?)}d)FrZ#sw{l+{9HEipn?;1BahF%AutusH??qry7Q?kFxD@8subgTR% z_pK+}!a4DdAH*ntz^g7n<5$rbR#gP!XDWR)Tj;$bbODCeMqa!HUFeM=XAqWn2u`4b zR`*6cD{oKfLC~hyU{|eAIhBvQTnJg$s37e8+UU4%{{?U#w_^# zt=zN%TBA)n<I$2TasC23th z9kyY$A?4s-l28-f$j)4nPl?*1i6R(L@jTCR_D!j_c_^==HrrS5A8|~En3)>`$~c-Q zZ*bg;O1pU7gd7K5T4PCeAlpLTGv{w~Rh_7vi#geaH@j3(K9gXG3|2|F^?Hst;zf=% z3W_%Ysof(+cQ{oUCbph=49+k}!Y{hDYY%SwY(B{j;qS2i!@PJJQ@DIJ#k*;G8Xg&# zb*1pkM_SWX-&Vhq9!Y#_S`L2OM@A1AiA`5l?n#~jnLzO`DFFWcDny3%*p4dWapnzZ zyQi8bf^XZw;crpM>eIGa0__K+zu47<<5^sZa7qz}UZSmUt!}~WWXldHcQDjx;7Js2 z!*!`)mJ^8=9UGCU?cumxnWGK;MO|olW6MBHi|+m^S9shNEUu^EMOkTNlM&nyukZ3d zzcIj`?(XJut8mtPo4cH=cX%B~R_$DS$Ge{M0;dPPv_QZ+k8>T+HSVr|f1U{LB50q1 z{SR7_DJ1i}88?BQ<65ysR$d_FRiv?` zW?)RWQn47U$7LH$JMBvRPhUl-ZRNdjJ;=g-2F;h!NtQ$Pg(0f0CT!L&EX`;by2&ne6C%R8P!nKf%{bc-x#40dsTZfOxCMMO}l zqW}$eQvG6X%znx0HasYx$!LUd&(SZykDwG+o<~)5897)j|ZvS>bEf74=<8$ zR6O-EtE(dbRe=O`2Cr>e(&gbiIHBsbqpj_);MgR`a}Sn8;#P(2{ecU9hFG~yl@zf~ zs_{5G7=_c_9Yfu^=2Heq++UuVV8=(7+LwdyW|NBCcQ=c#oC$p{azhO0utpNSdx% zJE$VNjCuUSqYgdOz|!A1^B^?0aJV3nxg6{0niGFBbb!_KZC}s)D;oZp*Lz@>K(gUw z>(}E>j~lW$A%Vdf?5@76Fnnc5A%$Dp$#o#QxgW_LOFZ{;i#rjt=gj8!pucX4PeRrC+8JFNI=+8 zGh3oXR2e)Jb0pQY0toS(yBxc9J@)dq!lC_`*4vgN(A2}h@HHojEc^{i65QLoO~OdZ z){T(b=_m{C#G4QODC%@c5#w={U_l;+qK|a;GaQr~Ap6L88qrfT+V*effs9S^;j2tp z8=G#?24stW#cIau>X%VX#dtTWn((snWnu9I8n%Kc6J{g`pqv3t<2xYl#CJ;&mS|zm(=wY;E_2pF!+Kh_e7P?y5B zKk=5sWs`e_{{ZAF&fr|YvVJZN@fAE>tau(_y?!-!4{DvLUl15!R$DFhye zRzTz2mNF+~9NTx!rLww+;o*Of&3zHwJ?`)Hg-&ySf9W{?0OC8B)=PAouq0n8{>GVE z7^?yZ;tsn>Z*LMS7#A*g7wDB~X=R~o+?N0WnPog!)|JxJX&2^@FOom*YTnxzl0mkuN2w@x}jaTv*sTU(UwB_+GZ4-j|{c6tAOUcAZ@pj~^ zR`A6HDHKBpqjyv4b+alfGaM)xCWAE2)4t;VQSP9t7`sOL%W&Qz9-Q0QN#tsP?r)B^ zA7k(|+v7h9Ulczoi1-C*n2Z`mem){3A3BCk))=Js(NE?z=`}GJF9+Z|5_+Rmxe)0i z?%c(n`qN&NwHCLIpkW`xm7wlBdqEjKD11LEX5=Wt6H1bl9ijpixqxYI6wc5(lzjLz z!a>!0mlln*(?D?iffZ?Y z9o&0Mxrdp0e&@{&?;hdE)x7cU8XewR@_m6cD34m!$|)C?~m0-cK*a%uG(&t56*}nkVXjAqc4qx z>{IV54jgq;T2o3*q$LdA7?;T%TU+B2IN$h3PW{ZCoGeEZxN6WA!h8D&`3LO}!8hyD zmjccPD!@qKi2gK*EySpb3p<<&v$KxUAp&n>zE|pdm(q5Qw!?%XS=IW(QXm>B8Eji6q-$xVAB#<<*+pVnjO=sPM`CkEKI97ck77oa!=HlMbs_R7&L9gD)z=H z(WbXwSC5+diVWz(Er#U$i0EiY?tCK*Jkg$i3Q0BrWXVs&oYIw*lro%yQ>SSYuRbEQ zOueO`Q^q#MKzl7aV*I31hOUOrmA@TCPx-HVNm@rJGR9Z5UpNPd*J*E%vN--x>@TF+ zlqb3<43I16j5)ZvpWHzh)JOo5a4Sn*3%ioza-i5xQCcR$>e2Snt|oEA++LIb8OJ?y zO0Y!ow*?dljyINHJ9yvxRg4W3Mc?+4c$?Fp_A~iF^{9O3;jbPR`P4o|sCR9TT8nc? zJ!?9)H5$#oWBKOQ`Xki5Glw4k~IH zoXGa`uFb@JMUl7$Td1z$+fRcpH9pk8+A4D( zpOE|hb?m0WZN2&7sBG+wS1v8#r@TKMFe-K={f?D+Qu zAKW5d}8HbrI4TWJZPY=qi6>T>we9T8fOz5Ye7V+#zc4GsCiru; z>YlZ@fI-M3AdPZHlE%@v%CO&1Oz9`pXE~4pV}JKle{s*bd1kk0HfSr3OmFd4=BA>O<40Vb-XXb{lM04t=ZR&EU9sG9>wLiObo{EP_2)5mklYtd4(MdSNc8Afy6+VU1mYHtAu=k_! zR5y?e8B!T3=1YU<_%AE_!lL zR;d>ENU)aXcHhH4*j3GwK$~JfSMoIkrYRNw06GK^2ssCbe*R1%#yOmtHDwb>aK2|? zgXdAku*Dop!4N3|#VP*)wFN^OTR@=(02onGF3S3y`sS5j ziREqzKQmiK7+K$YvD$A|HDhboT7_}rZxf$WOr=k;nE3tzqWz%OcprsD`!%FuUBTcf zKWQatA)Oz0{{XaWK#W+i>nmylCltyCDsUx&NQX2cv_z&ueYM2Q82ngk+;R@^Mh#5^ z+1^VKI4XPx^%bLZg2x|=bQ!}B_E_`&L)^KyTbQ;4)9w}F1<|Bzy z`;SB1zqEP6OAZChUP5><_f178l{SDNgN9SZNa77&*^8+1zqMa4ucc`F(`hjep2?0J zbUl5f=y0+xnK^Y7tQZCdapCuE`uA!W9JB2?00+0vv*GMLf8^_{3oF6)@GBpCgH`+@ z%Kms_E)^RCG?yxT`;8KSmx;!Cso-ih+)P6t`JzAChBk8-iZjMGfi<3OrC5}Km z&r?&}#{l2Gz0p(LNjqhZ;lOeltVu9$JA-cT&nIguw=M(WtLW9YYkhChFl0Kk-Pi$b zLE;0strKaooudpioBj*PQy^k9%ei>{tmz2y$K!vGAy%>dX7prcI55Z6wGo}ho+3CL z`v*C8_|$%N*8W!bRAcPd;Yy%0obM)w6S}mP?HzVXoOX4gBL4vCu=^5Ng8hArVGfHq!pZ&lEo6cQlj} zDNE~Z)51JM-%7Q*itYrOJ_m^CdUr42Xk6Yxx&HutJ>~busHJ6f45Sq!AdX_Rt)LmX zLH_`_KfP#s&9(Zo@llx1QPbK43Qo@8PVxJzBpi)W=v}Nl50HvRS6(zOA7-o&vp`@P=wBtU!>NxG1-DG{FZL+3C z868H@sO|2fSNgnxqm%`9jPmuR6WYpLN= zd7sYYx%i#}w0*I+wou-7@xR3PB*0%?SRJIRs=8f-2@2EG~dnKKxBqmnn4u%D)C35ANgMARZ#NSEXYd{{ZI8jU{1g zx@(jN-3Msam(!Lvwz6Bt420wrb5{2f+ex<>8NnOq>08tOpdSSl{{RubJST6EsZ#P` z+B_~m9$=WQ989szZhZS0U*=8XkZ)OHV;d30YU)`hirzw_!7j`CHE64DH%vTI4VZ^S z{{SstNOXw1yD=kX7=|RksY!SazS1 zz;G1|V&?WZ_@rJp{=>T=%VVVhlZx9>yEJ5vJU3Rsc5+7R8m%mn= zZSIZtKc}zX@%KI7bI){lanISGzRi0CB(6)py1VJ`?pw&I82^cd-+v0#x6w;fAkQ<&=(~k~!}Lv}i+=csOfDH4&;_I=@Mu9QPZ(-OKYbe$`+9x;`Z)j9i01ONY$zQ61utUB+g2m>A(6^ zDoeCeCL^5+Z3C4p$b(l}OG~(0Rg|bi*daF)ijo$H4YP(nhf=FC!h%glVIyyH6n z$#J;;ZfYgM=dDNLc7Kr8NKi>Q0A$k1kuA&qGF#xIfUOpd3_YuP<~wabf;Sy(!FbU* zB=@IsV!`XGFF2y%R0=FxjXw5_dghMq%USZLew6d5QEh3BL#CHqT4+B7g0|SsoL9H)q*v=CcjzbvuBh25|!d)X&D8$}b`UF?21q=s@Z zmd}rd1$|ER{{W)DHlbyE?nU|6)h{{Q#(saf_i#K8<5!S9jr6f|j!zHu6m;Y=#Ek@I zSqdp( z>k`ObSI+anK;p)(+-U2`E#tW_vBlJ2X0D}oVKxG|86TYC#xAgt8i@__EHkidLC)6SdwU-DF#*4gI1HtqBM)7zD(>(@Z{Rx z!-gEV{3>KdRtz?yb=h&$8nG{JEc!lEmw@FDR5f>QE^XwCnHgV*s~e`XyN%m$1gnoj zTe*MA*!#)Gwr!D|6VMJOn$S%rX>fLk@}NEyq>U4DBCo(>_h-_J9He)sICvzj4>$%$ z4;Jw+b=_k|cHjjwoe!gCx|em-+y_I+epM`FLllfjc?%LvT}_x_KzOJvR;k)ffbimd zmE_%=SRQBHPQO>OQNl^iPofQ0>9r<#F8a{%3oR0|N->bqy=dUq+C}bV;F*!Xu&72$ zIi~SQtH^IX!u`F(vwjiaPk`cUM=skx09 zA>fKz@~sUSTRzpi)47oMWR^Hlq>;iR@ngVJy2z^Pa;yOds6F4L+peq+s(ZYTSWkit=g@7Woy|{|(xE>is*Uz13Yo6Z8K{;rS z!Tadfj^;OKUhE&-S@wi~$#TUU(Mc48DC$**p{vVjv~A-=Pl+2S-A5x<*9&tB$h>$k z=zEqBeQxt;jKgd}{%Ls+m1+CKeWZoD9Hd8JdbcxLO8~%wfKx=1#jrc4&DB)T%53ATA zBgZk@KMqu*Wk9I6mwu;R~lVr?UYahhdXRR{u(3IP1Oze)}+EdF2o z>A0P4ex-Jkf%b3aq?B6TB-#5J@RE8r;c7arAX}Ivg}6GAR`W|?c(QYih+))lB9YQW zS3{Lx3ZFx&QGy(C2CraCm>mz5)U9ZFSP88p^G3vq($My{Y~15JRiNkWuX{7XWyj2Z z@*nelnbBiN+xS0kACuS8tuADR#TNDAnKY!6imQnxG3t!aHqzG(GTy& zoO;UjqmaDMA>Xn;Q(8r{agK^LStiU1ed15Cy_kR@Uo&y3z_PSh2kj-^`jHE;QO>@N z+oVf#c>9qTVQB;DO(ZtBJ7v8PQNv6C8;AC(P7u^%2%}xg_P2QaFpPiJ)oh)$_J0fu zpZbrqnX9a#?s1b%c2+sY3HjsE~uG^`y}#|8k8pYBj_ zL9RRXk-zR^AGnV#{n7jTPH01T4b8Isxbv^SRyVC`<~afE$=*4sphE!4K?GzGRuXW#BUOZZHtKn~&0N0R^0!tr0VDy$(x^pM9Dp9eSym{^pOETm z#vn`HPrzh~xS6Mwtt3T_g|?4QB|&pq1`v=9eZuW{M=IZ|N^M1`f1HGnCZKk76mk2-X9oK)VUnJjxP0c~Y zt@KkLB;REx$MBtcb^$emwl~B>)s@k+x`uK1Pl$C>#84rCWjO?7A4949603Euq*nWO zh55$8t4B@S*~G-}QLuh1T2emF`fiPe{Od(Y+Gy)@&=vf@duQ(C`!kRE4+f{wRu959k3WcUjDMJbm{&OObP6vRPpe}X+$ zG40K?cqabWoyg1 zWX?k!-wH^@y`(V?N-8mgT2B#M)rG;iw2DLL^1q`ld`}?!Rqv>^g829jwxC3xU+q*hZr=N&STv zu#0d&=ku=GVly4OqV;Xm^GJ6+&&dT99864oWNcG_=fjNqs43%k@wTLVYQp+!3zeQM z5&;Ar1RB1&ORKlJL?~SCGVuYb=JZc)6bvzM5TFS-s38!<7oQXCE+l!R7Vz&R_^Lvh zI@Bs}k-7TyqDeVAtl@2iMO@1<0DL(LzA2-uA<@wv85xw;jkVg6*b%+X+5SbTd{QdY zos}k`7*cwjM1=&gzyl=IM95xd<11(2yoFdVYjZsOkalbB80|4S;2X*A-(8DKc9V;r z3}dSru(dX};ahx+cH})+_pZ;^^sE-+d{G>?%a40W&)8U8IO11ed|HwRWt!_BW+V^~ zh#9L%J4<-aFfe{y-5DoicJQ7LoP0%DH)DT1KZbSfQ~8GG*Yt)y$4Lj}CH-owCLY${{U5eAN_^TUetfh*f`Jbxycsuk;*6jnyjM_ zHt~?7H7{;=N3uOleNeZGX&OV&9185`8={Ih$6VyoTaduWB$)bepphyQv9LMvG`6BKv7Lr0XkC^`eVOJBq#H{V|9R{m-#F7o5N{#J`&=$daT_DOsvxxDK=i038Oa_sS##jHiF14mC7_C|$Y@ z)ngA~YZ{+18%^;(&Sv~0=coe20Z)L$4g+;YyC^!I;UEG@zyl=Jx&Hu&-y5P2VFu`d z=H2W2wX?Q)KN0Yt^);dF`v^N%2eg;;UiZ>lpuLnn;4-~Kql$eP5ue&@cILEWL0qog=f=v=hBhK) zFoX!ybXu?mE19C`2>$@I_XTWrvEB}O+8-*>ZZ2%)fw^UVObHEF2( zO&M|3F!f^>)_#v%#0QSn;hAgb-NxO#(0h%vJ`JjE#Tven$CzS%lK%ivQ%l-MYNv#8 z;@}PQ4p<+@+j`5kxlS zJi1l2s=y4AFftwN=apYx8QWoZMi1w%)&lGf$tO5aIhs@&62o8+Zsy*{r9ltYt`Z&i zY_x}>3U^4?B!wIWOW}5pF%s*zR|$oHANnLN99vCNLr0HSJd(DX)E=!iwb_(hlEy!v@t?w zlpfYzyh8W1t?YMYFe9hV#YSt7|xHjPN`4>HEEiR@G06P4DQnqGQ`> z?%Rj#ARKdBmSd3!f*-ns}3^f(zIK47xF}Z7FXssiqWTH z=K2rfSy$lNu2ZzNxKDZ}74%i}E&L~gByUrXD;jdRmr)x0e{ zsSx7q1ppKUUI1Wx2mFi6{#E){ZHcY!M+ZWO;+mLG zi~E|oaIg&-#}NE0j_=n_kJ7hD`{0rCeKXF11OlWGMnN11?n?V39L}iY z+TM6m5UNc?yF|I}UtY&$4%=Y4+>6xJg`3~bZti?Z`Z4cmJs>P$@Zik;LH+AQ*fb#B zPm?^Ic&IwtYX@hI0kQ&(Q%xKY$ux%ugC?(Mp7wsTTt3QXVT zq(;V`KD!^N?p-#_59_+D2ZBS@s^s<>LMeKcYK-=JRs+gdT|X`3R~_9(E9mJdrY25J zZ2thGR-aREq#(H*)lbbR5Wq0tz!CGQcgEGF#&k^o0LW`VM6Y=6c*h>^`80w*w{SJK~EEFRw+_YZ4oiCOlg_YCjaL)c!eDdTP?wO%y8xU$&g= ztwj>U8b-VnVy%<6y)Jx^3=da5@1zi7XlLZ>vVJRCFG`zCMK(+ys#3DLt1AXlG6>*C zreL6xM3y9)L@s?Rg*Ie;k>wm{U~9a)iq0O=PlB&DH3SzLUhSon;Rl(z4|Qn~BSl0| zs-pl7?vc`QSs_&+r{tYIO=#;0CYS5io87mnoo@RB)bORsE?apfhT*)>tvRFw(tTbw z`zVfF^=kSvW1YPvD*5~ndM>>u{{Y{V z$Ntd$+n?T`6U+D4(p?qC{t^EGyg$8LT}f=&UFv+wcp4DLKigS8!;UeUOT0G3>QU$r z!ytk^J%<64QeHru_K&46*Ka(7Vm`GEoxQ5_7T)pMl@%E7z%%@Ro@l1bKx&cQ`! zZ4i?KJpKzv_p^KTJ;0a=RPy#wGxOj+ zCamE4y}BXud7t8Y+D6pf*~u95j}m-Fs|_VJlVi^b;-B4W3#cw4?P73OpHb*VJC`*a zW&H&J0Knl)Pn2;#prufw16}3pHc@TKyq_WtENV5n()Wb9;m6%Ys34BsB#JSGU)R+4 zITOfL{+v;mtv8+>ulTJUqFZn4SD9%J@QwXjTew}^Tcu}ZL4Gu#leFA<4wdI`;UB53 zGo-$&A7K-HyN*8M)s>U!JGHf3h?fmJbY7IGh^mO8RX_@(fCF9mEvMhnlzvOC*dntY z091}$nU6D?iFP7I=%TOJYSvFo8iDhopgiNaQTa6eoQI+jqt4{yG30803%n?!SNJen*haFbFwinL~i}Im+Z`A%( z_1k}V{#0+R$2LDIH|l(mF|EBJFOm|Y;r?i7>#0s=ln>UD&d$_rNI^Ybr_!;m=vQd< zJB3X=v$kY%dy&S{9177|COHt?TprHRJkQO~K}=ffV`@Ii**|3u!<$v@-_~3O$c=b< z`qqfH`po9>$JsaU9Q?j&$K6NL8Ftm%ZpjMZ-Y2}YcLVQoYW6^05 zV?{(5RR94|r`9giagGh`>gRy=8fMJfTz1Gl&QqU2)NIV~8;ej_F6m?ZNPRl3MKrL! z+)h=h%S&6zLfqme>-bF!5D3OO5yMaz*L{Btig>(IA1YKZ-RXOlW`E~?NID9)w~E#w z6ib2nlhpSv^sS|)RJPon;#Bu{+E!r_DaR<+fc0ZqKSaf6i60L)UQ6)S(h_!K-g?)D zEf^O-l|BLbn)<7x(8m6D0oZ$^RLU9&SE?GaZ0PHe(rC>dCbX2j zj*q*3kq|z6t3bbHb9o>hAqpbZ&B9y6&inI_2R0_Yj7f{Di2eAgO~}=y+h;^g>M>tO z7sGbms6YNrY4*jWOV(N(MO^5_lPwMrx^guvGD6!jAyYt=IRt}I=^Lp303Oi(DqSz{ z!}%Kj0EpQ>=Y;&!8v53ZJYygBSJw1s;Di3kI#ShqsQ&;U1wNPeA^EYXG=!bHhBlZe3A!j($r%3Tw2k&jf+Ohd z{i--5yqsFc@s+*QsN+bHBaK3$j40J}Hpl&p6*YPjp z>t9o|(vbG)x%rXAA6lP9UBjF&XuVgDPmlZ?MjGdqt^_fY;In+0lr;^R0azOj$_&{J-)@`_~`eI(9v~ zApYcl(BeKinHAS%)G+93O&J7)lqEj^cR`50QBR_$9#%%CvAbXNTE4Au{>%PGg{{x~ zFZoy1=FT<^Evuh1D$|IDiT7xiE~y%qmCg}sN7*fu8yVV>j%0qVs8zSx-pKrybs@sL^WZ62A&I1s zu(-)Sf|Z|4UzwZ{Zx;pC3U>EdK1e@TKjYXE0Le7&U*T6eId37u_*{7!qL%MQT^-vl zH!>a!D%SSe`aPxMJj?i&r?iT3_K_r`*N^8?=sRa8vYFyO>x_z*LSLtq7E#qr81F5Z zt|p9*nG{{wnadKy$+phVhck~_+P+MEu3_}v9!;Yqr7m2N@AAs2%O%0HIBWP={9SYE7;zCx!yf`+LdvyI=Ns{|q~qs6z@&eJ4)IM$ZFYwLxyw%-_R ze>tmbbhU+IPFzn@QpjUp2+OhALDZVJfI$kz4oKd|s~#u5%oJqPGe~jy3P^(QJI<@y z7%yYlkYR_F#}%pVuJml`Nh(K@Mc``7OhYjVIS|S3k0ViL$BhFi<|%tKX ztY zXyX-`B;nx|J=LX(=2-x4oLJTFnz_2lMVO-W^BT8?d%3#{NcQCBe*${3tq(d_=`PE& z679>`H0RG#+_c+k41zdQ_LH5!dM#L9=#w{LZ74E(#ChvZm|(GJGxn2CBvq{uVAd9; zWNgVJZNEXNRfLhIl+r590eJ{tTpE^GphEJvRUFM-X=2dqLJG6<<`?{$v$gD3X@`uD z$ZNQnV2PNW`zK^|ucPQ*_Af$T@)b161TxJ2Sk$qoB7G^OQ6%9bnPv1*P{S3xVmOd0 z4g*w!A4x4eP*ZQ2M?qSB{5Uv` zQ*#K+sys{Y3b&90vUna8PASCJlOD+CHr^EQ_*7T$u>xGi-3

2Z4bdL9Ad^#G6f$ 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 z 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 From 7d004d1585926535b8d2681369e68eb79ec63bf9 Mon Sep 17 00:00:00 2001 From: besscroft Date: Sat, 12 Oct 2024 14:07:53 +0800 Subject: [PATCH 04/11] =?UTF-8?q?pub:=20=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-next.yaml | 36 ------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/build-next.yaml diff --git a/.github/workflows/build-next.yaml b/.github/workflows/build-next.yaml deleted file mode 100644 index 2faab8b..0000000 --- a/.github/workflows/build-next.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: Dev Docker Multi-arch Image CI & CD - -on: - push: - branches: - - next - -jobs: - build: - name: Running Compile Next Multi-arch Docker Image - runs-on: ubuntu-latest - steps: - - name: Checkout PicImpact - uses: actions/checkout@v4 - - name: Get Version - id: get_version - run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - id: set_up_buildx - uses: docker/setup-buildx-action@v3 - - name: Build and push next - id: docker_build - uses: docker/build-push-action@v5 - with: - context: ./ - file: ./Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/picimpact:dev From fd5c42150dd3f9a297c86f7f6662bf60623b8fff Mon Sep 17 00:00:00 2001 From: besscroft Date: Sun, 13 Oct 2024 16:52:36 +0800 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hono/file.ts | 2 +- hono/image.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hono/file.ts b/hono/file.ts index caece4f..2d4d09e 100644 --- a/hono/file.ts +++ b/hono/file.ts @@ -7,7 +7,7 @@ import { Hono } from 'hono' const app = new Hono() -app.get('/upload', async (c) => { +app.post('/upload', async (c) => { const formData = await c.req.formData() const file = formData.get('file') diff --git a/hono/image.ts b/hono/image.ts index f9d87a1..102723e 100644 --- a/hono/image.ts +++ b/hono/image.ts @@ -11,7 +11,7 @@ import { Hono } from 'hono' const app = new Hono() -app.post('/image-add', async (c) => { +app.post('/add', async (c) => { const image = await c.req.json() if (!image.url) { return c.json({ From 751c55dac713616ba1e0f324474ab26a6b79e5ee Mon Sep 17 00:00:00 2001 From: besscroft Date: Sun, 13 Oct 2024 17:49:53 +0800 Subject: [PATCH 06/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9B=B8=E5=86=8C=E5=90=8E=E6=97=A0=E6=B3=95=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E5=9B=BE=E7=89=87=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/query.ts | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/server/db/query.ts b/server/db/query.ts index 00aeaec..d772846 100644 --- a/server/db/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 From 48ffb63c40354ce5cf83cfc5ebfa6d8ce84aa91c Mon Sep 17 00:00:00 2001 From: besscroft Date: Mon, 14 Oct 2024 18:31:59 +0800 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=20shadcn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.json | 7 +- style/globals.css | 15 +++++ tailwind.config.ts | 155 +++++++++++++++++++++++++-------------------- 3 files changed, 107 insertions(+), 70 deletions(-) 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/style/globals.css b/style/globals.css index 7c4e738..99a19a6 100644 --- a/style/globals.css +++ b/style/globals.css @@ -63,6 +63,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 +103,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 From 38b815fc23652d05eac9635423b1778ac1eab68a Mon Sep 17 00:00:00 2001 From: besscroft Date: Mon, 14 Oct 2024 19:02:40 +0800 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/layout/DropMenu.tsx | 21 ++++++++------ components/ui/avatar.tsx | 50 ++++++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 43 +++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 components/ui/avatar.tsx diff --git a/components/layout/DropMenu.tsx b/components/layout/DropMenu.tsx index 8ce672f..a080b2e 100644 --- a/components/layout/DropMenu.tsx +++ b/components/layout/DropMenu.tsx @@ -1,6 +1,6 @@ '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/actions' @@ -8,6 +8,11 @@ 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 { + Avatar, + AvatarFallback, + AvatarImage, +} from "~/components/ui/avatar" export const DropMenu = () => { const router = useRouter() @@ -27,13 +32,13 @@ export const DropMenu = () => { return ( - + + + CN + { session ? 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/package.json b/package.json index 60b1b57..029028f 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b42d66..a85b3ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@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) @@ -1266,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: @@ -1315,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: @@ -6589,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) @@ -6627,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 From c6dc608793ceba8205dea5587e682576269c486f Mon Sep 17 00:00:00 2001 From: besscroft Date: Mon, 14 Oct 2024 23:34:00 +0800 Subject: [PATCH 09/11] =?UTF-8?q?fix(style):=20=E8=A7=84=E8=8C=83=E6=9A=97?= =?UTF-8?q?=E9=BB=91=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/layout.tsx | 2 +- app/providers/next-ui-providers.tsx | 2 +- components/layout/DropMenu.tsx | 6 +++--- components/layout/VaulDrawer.tsx | 7 ++++--- style/globals.css | 5 +++++ 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 1aad4c2..1648417 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -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/layout/DropMenu.tsx b/components/layout/DropMenu.tsx index a080b2e..f4d9990 100644 --- a/components/layout/DropMenu.tsx +++ b/components/layout/DropMenu.tsx @@ -7,7 +7,7 @@ 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, @@ -81,7 +81,7 @@ export const DropMenu = () => { : } + startContent={theme === 'light' ? : } onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} > { theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' } @@ -106,7 +106,7 @@ export const DropMenu = () => { : } + startContent={theme === 'light' ? : } onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} > { theme === 'light' ? '切换至⌈常夜⌋' : '切换至⌈白夜⌋' } diff --git a/components/layout/VaulDrawer.tsx b/components/layout/VaulDrawer.tsx index 38adc30..609d33f 100644 --- a/components/layout/VaulDrawer.tsx +++ b/components/layout/VaulDrawer.tsx @@ -19,7 +19,8 @@ import { Settings, LogOut, Copyright, - Info + Info, + Orbit, } from 'lucide-react' import { loginOut } from '~/server/actions' @@ -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/style/globals.css b/style/globals.css index 99a19a6..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; } From b4c615c94316a45441635037b9be5025558fd7a1 Mon Sep 17 00:00:00 2001 From: besscroft Date: Mon, 14 Oct 2024 23:41:57 +0800 Subject: [PATCH 10/11] =?UTF-8?q?fix(style):=20=E8=B0=83=E6=95=B4=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E8=AF=A6=E6=83=85=E9=A1=B5=E6=8C=89=E9=92=AE=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/MasonryItem.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) 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() { {MasonryViewData?.exif?.model && MasonryViewData?.exif?.f_number From 150e2a303cbebbe733def39223252a4a6283e6b7 Mon Sep 17 00:00:00 2001 From: besscroft Date: Tue, 15 Oct 2024 21:50:13 +0800 Subject: [PATCH 11/11] =?UTF-8?q?doc:=20=E5=A4=84=E7=90=86=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/about/page.tsx | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/package.json b/package.json index 029028f..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",