Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
47777acd74
|
|||
32d98f4bc0
|
|||
ef60832bc2
|
|||
4f782560de
|
|||
979cf7320e
|
|||
1f9f11e1be
|
|||
1d183c37f8
|
@ -1,30 +0,0 @@
|
|||||||
version: "1"
|
|
||||||
name: Publish Development Docker Image
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v[0-9]+\\.[0-9]+\\.[0-9]+-dev"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Publish Development Docker Image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Login to Registry
|
|
||||||
run: echo "${{ secrets.ACCESS_TOKEN }}" | docker login git.altaiar.dev -u "${{ secrets.USERNAME }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Build & Tag Image
|
|
||||||
run: |
|
|
||||||
docker build --build-arg APP_VERSION=${{ gitea.ref_name }} -t git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} .
|
|
||||||
docker tag git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} git.altaiar.dev/${{ gitea.repository }}:dev
|
|
||||||
|
|
||||||
- name: Push Images
|
|
||||||
run: |
|
|
||||||
docker push git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }}
|
|
||||||
docker push git.altaiar.dev/${{ gitea.repository }}:dev
|
|
@ -1,28 +1,36 @@
|
|||||||
version: "1"
|
version: "1"
|
||||||
name: Publish Docker Image
|
name: Publish Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v[0-9]+\\.[0-9]+\\.[0-9]+"
|
- "*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Publish Docker Image
|
name: Publish Docker Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Registry
|
- name: Login to Registry
|
||||||
run: echo "${{ secrets.ACCESS_TOKEN }}" | docker login git.altaiar.dev -u "${{ secrets.USERNAME }}" --password-stdin
|
run: echo "${{ secrets.ACCESS_TOKEN }}" \
|
||||||
|
| docker login git.altaiar.dev -u "${{ secrets.USERNAME }}" --password-stdin
|
||||||
|
|
||||||
- name: Build & Tag Image
|
- name: Build & Tag Image
|
||||||
run: |
|
run: |
|
||||||
docker build --build-arg APP_VERSION=${{ gitea.ref_name }} -t git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} .
|
docker build \
|
||||||
docker tag git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} git.altaiar.dev/${{ gitea.repository }}:latest
|
--build-arg APP_VERSION=${{ gitea.ref_name }} \
|
||||||
|
--label org.opencontainers.image.version=${{ gitea.ref_name }} \
|
||||||
|
-t git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} .
|
||||||
|
docker tag \
|
||||||
|
git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} \
|
||||||
|
git.altaiar.dev/${{ gitea.repository }}:latest
|
||||||
|
|
||||||
- name: Push Images
|
- name: Push Images
|
||||||
run: |
|
run: |
|
||||||
|
67
Dockerfile
67
Dockerfile
@ -1,15 +1,9 @@
|
|||||||
# base
|
FROM node:lts-alpine AS base
|
||||||
# ----
|
|
||||||
FROM node:20-bookworm-slim AS base
|
|
||||||
|
|
||||||
RUN corepack enable
|
ARG APP_VERSION=dev
|
||||||
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
# We tried to make the Dockerfile as lean as possible. In some cases, that means we excluded a dependency your project needs.
|
RUN apk add --no-cache openssl && corepack enable
|
||||||
# By far the most common is Python. If you're running into build errors because `python3` isn't available,
|
|
||||||
# add `python3 make gcc \` before the `openssl \` line below and in other stages as necessary:
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
openssl \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
WORKDIR /home/node/app
|
WORKDIR /home/node/app
|
||||||
@ -30,13 +24,8 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
|
|||||||
COPY --chown=node:node redwood.toml .
|
COPY --chown=node:node redwood.toml .
|
||||||
COPY --chown=node:node graphql.config.js .
|
COPY --chown=node:node graphql.config.js .
|
||||||
|
|
||||||
# api build
|
|
||||||
# ---------
|
|
||||||
FROM base AS api_build
|
FROM base AS api_build
|
||||||
|
|
||||||
# If your api side build relies on build-time environment variables,
|
|
||||||
# specify them here as ARGs. (But don't put secrets in your Dockerfile!)
|
|
||||||
|
|
||||||
ARG ADDRESS_PROD
|
ARG ADDRESS_PROD
|
||||||
ARG ADDRESS_DEV
|
ARG ADDRESS_DEV
|
||||||
ARG DOMAIN
|
ARG DOMAIN
|
||||||
@ -56,8 +45,6 @@ ARG APP_VERSION
|
|||||||
COPY --chown=node:node api api
|
COPY --chown=node:node api api
|
||||||
RUN yarn rw build api
|
RUN yarn rw build api
|
||||||
|
|
||||||
# web prerender build
|
|
||||||
# -------------------
|
|
||||||
FROM api_build AS web_build_with_prerender
|
FROM api_build AS web_build_with_prerender
|
||||||
|
|
||||||
ARG FIRST_NAME
|
ARG FIRST_NAME
|
||||||
@ -70,13 +57,11 @@ ARG API_ADDRESS_PROD
|
|||||||
ARG API_ADDRESS_DEV
|
ARG API_ADDRESS_DEV
|
||||||
ARG APP_VERSION
|
ARG APP_VERSION
|
||||||
|
|
||||||
ENV APP_VERSION=$APP_VERSION
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
COPY --chown=node:node web web
|
COPY --chown=node:node web web
|
||||||
RUN yarn rw build web
|
RUN yarn rw build web
|
||||||
|
|
||||||
# web build
|
|
||||||
# ---------
|
|
||||||
FROM base AS web_build
|
FROM base AS web_build
|
||||||
|
|
||||||
ARG FIRST_NAME
|
ARG FIRST_NAME
|
||||||
@ -89,20 +74,17 @@ ARG API_ADDRESS_PROD
|
|||||||
ARG API_ADDRESS_DEV
|
ARG API_ADDRESS_DEV
|
||||||
ARG APP_VERSION
|
ARG APP_VERSION
|
||||||
|
|
||||||
ENV APP_VERSION=$APP_VERSION
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
COPY --chown=node:node web web
|
COPY --chown=node:node web web
|
||||||
RUN yarn rw build web --no-prerender
|
RUN yarn rw build web --no-prerender
|
||||||
|
|
||||||
# api serve
|
FROM node:lts-alpine AS api_serve
|
||||||
# ---------
|
|
||||||
FROM node:20-bookworm-slim AS api_serve
|
|
||||||
|
|
||||||
RUN corepack enable
|
RUN apk add --no-cache openssl && corepack enable
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN mkdir -p /home/node/app/api/files_prod \
|
||||||
openssl \
|
&& chown -R node:node /home/node/app/api/files_prod
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
WORKDIR /home/node/app
|
WORKDIR /home/node/app
|
||||||
@ -129,20 +111,11 @@ COPY --chown=node:node --from=api_build /home/node/app/node_modules/.prisma /hom
|
|||||||
ARG APP_VERSION
|
ARG APP_VERSION
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV APP_VERSION=$APP_VERSION
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
# default api serve command
|
|
||||||
# ---------
|
|
||||||
# If you are using a custom server file, you must use the following
|
|
||||||
# command to launch your server instead of the default api-server below.
|
|
||||||
# This is important if you intend to configure GraphQL to use Realtime.
|
|
||||||
|
|
||||||
CMD [ "./api/dist/server.js" ]
|
CMD [ "./api/dist/server.js" ]
|
||||||
# CMD [ "node_modules/.bin/rw-server", "api" ]
|
|
||||||
|
|
||||||
# web serve
|
FROM node:lts-alpine AS web_serve
|
||||||
# ---------
|
|
||||||
FROM node:20-bookworm-slim AS web_serve
|
|
||||||
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
@ -167,29 +140,15 @@ COPY --chown=node:node graphql.config.js .
|
|||||||
COPY --chown=node:node --from=web_build /home/node/app/web/dist /home/node/app/web/dist
|
COPY --chown=node:node --from=web_build /home/node/app/web/dist /home/node/app/web/dist
|
||||||
|
|
||||||
ARG APP_VERSION
|
ARG APP_VERSION
|
||||||
ENV APP_VERSION=$APP_VERSION
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
API_PROXY_TARGET=http://api:8911
|
API_PROXY_TARGET=http://api:8911
|
||||||
|
|
||||||
# We use the shell form here for variable expansion.
|
|
||||||
CMD "node_modules/.bin/rw-web-server" "--api-proxy-target" "$API_PROXY_TARGET"
|
CMD "node_modules/.bin/rw-web-server" "--api-proxy-target" "$API_PROXY_TARGET"
|
||||||
|
|
||||||
# console
|
|
||||||
# -------
|
|
||||||
FROM base AS console
|
FROM base AS console
|
||||||
|
|
||||||
# To add more packages:
|
|
||||||
#
|
|
||||||
# ```
|
|
||||||
# USER root
|
|
||||||
#
|
|
||||||
# RUN apt-get update && apt-get install -y \
|
|
||||||
# curl
|
|
||||||
#
|
|
||||||
# USER node
|
|
||||||
# ```
|
|
||||||
|
|
||||||
COPY --chown=node:node api api
|
COPY --chown=node:node api api
|
||||||
COPY --chown=node:node web web
|
COPY --chown=node:node web web
|
||||||
COPY --chown=node:node scripts scripts
|
COPY --chown=node:node scripts scripts
|
||||||
|
@ -76,11 +76,6 @@ volumes:
|
|||||||
postgres:
|
postgres:
|
||||||
files: # For persistent file storage across upgrades
|
files: # For persistent file storage across upgrades
|
||||||
```
|
```
|
||||||
## Fix Files Ownership
|
|
||||||
The `files` volume in Docker is owned by `root`, since the portfolio container runs under the `node` user, file uploads will fail. Run this command to give ownership to the `node` user:
|
|
||||||
```
|
|
||||||
sudo docker exec -u root portfolio chown -R node:node /home/node/app/api/files_prod
|
|
||||||
```
|
|
||||||
## Logging In
|
## Logging In
|
||||||
- Once the container is up and running, head to `/login` (`https://portfolio.example.com/login`), default credentials are below
|
- Once the container is up and running, head to `/login` (`https://portfolio.example.com/login`), default credentials are below
|
||||||
- If you would like to change the password, head to `/forgot-password` (`https://portfolio.example.com/forgot-password`), the username is `admin`
|
- If you would like to change the password, head to `/forgot-password` (`https://portfolio.example.com/forgot-password`), the username is `admin`
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^9.0.1",
|
"@fastify/cors": "^9.0.1",
|
||||||
"@fastify/rate-limit": "^9.1.0",
|
"@fastify/rate-limit": "^9.1.0",
|
||||||
"@redwoodjs/api": "8.4.0",
|
"@redwoodjs/api": "8.6.1",
|
||||||
"@redwoodjs/api-server": "8.4.0",
|
"@redwoodjs/api-server": "8.6.1",
|
||||||
"@redwoodjs/auth-dbauth-api": "8.4.0",
|
"@redwoodjs/auth-dbauth-api": "8.6.1",
|
||||||
"@redwoodjs/graphql-server": "8.4.0",
|
"@redwoodjs/graphql-server": "8.6.1",
|
||||||
"@tus/file-store": "^2.0.0",
|
"@tus/file-store": "1.4.0",
|
||||||
"@tus/server": "^2.0.0",
|
"@tus/server": "1.7.0",
|
||||||
"countries-list": "^3.1.1",
|
"countries-list": "^3.1.1",
|
||||||
"graphql-scalars": "^1.23.0",
|
"graphql-scalars": "^1.23.0",
|
||||||
"nodemailer": "^6.9.14"
|
"nodemailer": "^6.9.14"
|
||||||
|
@ -2,10 +2,17 @@ import type { FastifyReply } from 'fastify'
|
|||||||
|
|
||||||
import { isProduction } from '@redwoodjs/api/logger'
|
import { isProduction } from '@redwoodjs/api/logger'
|
||||||
|
|
||||||
export const setCorsHeaders = (res: FastifyReply) => {
|
export const setCorsHeaders = (
|
||||||
|
res: FastifyReply,
|
||||||
|
isPublic: boolean = false
|
||||||
|
) => {
|
||||||
res.raw.setHeader(
|
res.raw.setHeader(
|
||||||
'Access-Control-Allow-Origin',
|
'Access-Control-Allow-Origin',
|
||||||
isProduction ? process.env.ADDRESS_PROD : process.env.ADDRESS_DEV
|
isPublic
|
||||||
|
? '*'
|
||||||
|
: isProduction
|
||||||
|
? process.env.ADDRESS_PROD
|
||||||
|
: process.env.ADDRESS_DEV
|
||||||
)
|
)
|
||||||
res.raw.setHeader(
|
res.raw.setHeader(
|
||||||
'Access-Control-Allow-Methods',
|
'Access-Control-Allow-Methods',
|
||||||
@ -16,4 +23,9 @@ export const setCorsHeaders = (res: FastifyReply) => {
|
|||||||
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Tus-Resumable, Upload-Length, Upload-Metadata, Upload-Offset'
|
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Tus-Resumable, Upload-Length, Upload-Metadata, Upload-Offset'
|
||||||
)
|
)
|
||||||
res.raw.setHeader('Access-Control-Allow-Credentials', 'true')
|
res.raw.setHeader('Access-Control-Allow-Credentials', 'true')
|
||||||
|
res.raw.setHeader(
|
||||||
|
'Access-Control-Expose-Headers',
|
||||||
|
'Upload-Offset, Upload-Length, Upload-Metadata, Tus-Version,' +
|
||||||
|
'Tus-Resumable, Tus-Max-Size, Tus-Extension, Tus-Checksum-Algorithm'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,22 @@ interface User {
|
|||||||
resetTokenExpiresAt: Date | null
|
resetTokenExpiresAt: Date | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleTusUpload = async (
|
export const handleTusUpload = (
|
||||||
req: FastifyRequest,
|
req: FastifyRequest,
|
||||||
res: FastifyReply,
|
res: FastifyReply,
|
||||||
tusHandler: Server,
|
tusHandler: Server,
|
||||||
isPublicEndpoint: boolean
|
isPublicEndpoint: boolean
|
||||||
) => {
|
) => {
|
||||||
|
res.hijack()
|
||||||
|
|
||||||
|
if (req.method === 'GET' && isPublicEndpoint) {
|
||||||
|
setCorsHeaders(res)
|
||||||
|
}
|
||||||
|
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
if (req.method === 'OPTIONS') handleOptionsRequest(res)
|
if (req.method === 'OPTIONS') handleOptionsRequest(res)
|
||||||
else if (isPublicEndpoint && req.method === 'GET')
|
else if (isPublicEndpoint && req.method === 'GET')
|
||||||
await tusHandler.handle(req.raw, res.raw)
|
void tusHandler.handle(req.raw, res.raw)
|
||||||
else if (['GET', 'POST', 'HEAD', 'PATCH'].includes(req.method)) {
|
else if (['GET', 'POST', 'HEAD', 'PATCH'].includes(req.method)) {
|
||||||
if (req.headers.cookie) handleAuthenticatedRequest(req, res, tusHandler)
|
if (req.headers.cookie) handleAuthenticatedRequest(req, res, tusHandler)
|
||||||
else {
|
else {
|
||||||
@ -40,8 +46,8 @@ export const handleTusUpload = async (
|
|||||||
res.raw.end('Method not allowed')
|
res.raw.end('Method not allowed')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setCorsHeaders(res)
|
setCorsHeaders(res, isPublicEndpoint)
|
||||||
await tusHandler.handle(req.raw, res.raw)
|
void tusHandler.handle(req.raw, res.raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import Cors from '@fastify/cors'
|
import Cors from '@fastify/cors'
|
||||||
import RateLimit from '@fastify/rate-limit'
|
import RateLimit from '@fastify/rate-limit'
|
||||||
|
import { FileStore } from '@tus/file-store'
|
||||||
|
import { Server } from '@tus/server'
|
||||||
|
|
||||||
import { isProduction } from '@redwoodjs/api/logger'
|
import { isProduction } from '@redwoodjs/api/logger'
|
||||||
import { createServer } from '@redwoodjs/api-server'
|
import { createServer } from '@redwoodjs/api-server'
|
||||||
@ -15,8 +17,6 @@ enum Theme {
|
|||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const { countries } = await import('countries-list')
|
const { countries } = await import('countries-list')
|
||||||
const { FileStore } = await import('@tus/file-store')
|
|
||||||
const { Server } = await import('@tus/server')
|
|
||||||
|
|
||||||
if (!Object.keys(countries).includes(process.env.COUNTRY))
|
if (!Object.keys(countries).includes(process.env.COUNTRY))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -65,12 +65,14 @@ enum Theme {
|
|||||||
(_request, _payload, done) => done(null)
|
(_request, _payload, done) => done(null)
|
||||||
)
|
)
|
||||||
|
|
||||||
server.all('/files', (req, res) =>
|
server.all('/files', (req, res) => {
|
||||||
|
res.hijack()
|
||||||
handleTusUpload(req, res, tusServer, false)
|
handleTusUpload(req, res, tusServer, false)
|
||||||
)
|
})
|
||||||
server.all('/files/*', (req, res) =>
|
server.all('/files/*', (req, res) => {
|
||||||
|
res.hijack()
|
||||||
handleTusUpload(req, res, tusServer, true)
|
handleTusUpload(req, res, tusServer, true)
|
||||||
)
|
})
|
||||||
|
|
||||||
await server.start()
|
await server.start()
|
||||||
})()
|
})()
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"module": "NodeNext",
|
"module": "Node16",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "node16",
|
||||||
"skipLibCheck": false,
|
"skipLibCheck": false,
|
||||||
"rootDirs": [
|
"rootDirs": [
|
||||||
"./src",
|
"./src",
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redwoodjs/auth-dbauth-setup": "8.4.0",
|
"@redwoodjs/auth-dbauth-setup": "8.6.1",
|
||||||
"@redwoodjs/core": "8.4.0",
|
"@redwoodjs/core": "8.6.1",
|
||||||
"@redwoodjs/project-config": "8.4.0",
|
"@redwoodjs/project-config": "8.6.1",
|
||||||
"prettier-plugin-tailwindcss": "0.4.1"
|
"prettier-plugin-tailwindcss": "0.4.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
"@icons-pack/react-simple-icons": "^10.0.0",
|
"@icons-pack/react-simple-icons": "^10.0.0",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@redwoodjs/auth-dbauth-web": "8.4.0",
|
"@redwoodjs/auth-dbauth-web": "8.6.1",
|
||||||
"@redwoodjs/forms": "8.4.0",
|
"@redwoodjs/forms": "8.6.1",
|
||||||
"@redwoodjs/router": "8.4.0",
|
"@redwoodjs/router": "8.6.1",
|
||||||
"@redwoodjs/web": "8.4.0",
|
"@redwoodjs/web": "8.6.1",
|
||||||
"@redwoodjs/web-server": "8.4.0",
|
"@redwoodjs/web-server": "8.6.1",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tiptap/extension-link": "^2.8.0",
|
"@tiptap/extension-link": "^2.8.0",
|
||||||
"@tiptap/extension-text-style": "^2.8.0",
|
"@tiptap/extension-text-style": "^2.8.0",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"react-pdf": "^9.2.1"
|
"react-pdf": "^9.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redwoodjs/vite": "8.4.0",
|
"@redwoodjs/vite": "8.6.1",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.55",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
"@types/react-html-parser": "^2",
|
"@types/react-html-parser": "^2",
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { mdiOpenInNew } from '@mdi/js';
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import Icon from '@mdi/react';
|
|
||||||
import { useState } from 'react'
|
import { mdiOpenInNew } from '@mdi/js'
|
||||||
import { Document, Page as PdfPage, pdfjs } from "react-pdf";
|
import Icon from '@mdi/react'
|
||||||
import "react-pdf/dist/Page/AnnotationLayer.css";
|
import { Document, Page as PdfPage, pdfjs } from 'react-pdf'
|
||||||
import "react-pdf/dist/Page/TextLayer.css";
|
|
||||||
|
import 'react-pdf/dist/Page/AnnotationLayer.css'
|
||||||
|
import 'react-pdf/dist/Page/TextLayer.css'
|
||||||
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
"pdfjs-dist/build/pdf.worker.min.mjs",
|
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||||
import.meta.url
|
import.meta.url
|
||||||
).toString();
|
).toString()
|
||||||
|
|
||||||
interface PDFProps {
|
interface PDFProps {
|
||||||
url: string
|
url: string
|
||||||
@ -16,14 +18,29 @@ interface PDFProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PDF = ({ url, form = false }: PDFProps) => {
|
const PDF = ({ url, form = false }: PDFProps) => {
|
||||||
const [numPages, setNumPages] = useState<number>(0);
|
const [numPages, setNumPages] = useState<number>(0)
|
||||||
function onLoadSuccess({ numPages }: { numPages: number }) {
|
function onLoadSuccess({ numPages }: { numPages: number }) {
|
||||||
setNumPages(numPages);
|
setNumPages(numPages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [containerWidth, setContainerWidth] = useState<number>(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function updateWidth() {
|
||||||
|
if (containerRef.current) {
|
||||||
|
setContainerWidth(containerRef.current.clientWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateWidth()
|
||||||
|
window.addEventListener('resize', updateWidth)
|
||||||
|
return () => window.removeEventListener('resize', updateWidth)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="overflow-y-auto flex justify-center"
|
ref={containerRef}
|
||||||
|
className="overflow-auto flex justify-center"
|
||||||
style={{
|
style={{
|
||||||
width: 'calc(100vw - 1rem)',
|
width: 'calc(100vw - 1rem)',
|
||||||
height: `calc(100vh - ${form ? '8.5rem' : '6rem'})`,
|
height: `calc(100vh - ${form ? '8.5rem' : '6rem'})`,
|
||||||
@ -39,7 +56,11 @@ const PDF = ({ url, form = false }: PDFProps) => {
|
|||||||
</a>
|
</a>
|
||||||
<Document file={url} onLoadSuccess={onLoadSuccess}>
|
<Document file={url} onLoadSuccess={onLoadSuccess}>
|
||||||
{Array.from({ length: numPages }, (_, i) => (
|
{Array.from({ length: numPages }, (_, i) => (
|
||||||
<PdfPage key={i} pageNumber={i + 1} width={800} />
|
<PdfPage
|
||||||
|
key={i}
|
||||||
|
pageNumber={i + 1}
|
||||||
|
width={Math.min(containerWidth, 800)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Document>
|
</Document>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,7 @@ const Uploader = ({
|
|||||||
onBeforeUpload: (files) => {
|
onBeforeUpload: (files) => {
|
||||||
for (const [key, file] of Object.entries(files)) {
|
for (const [key, file] of Object.entries(files)) {
|
||||||
instance.setFileMeta(key, {
|
instance.setFileMeta(key, {
|
||||||
name: `${new Date().getTime().toString()}.${file.extension}`,
|
name: new Date().getTime().toString(),
|
||||||
type: file.type,
|
type: file.type,
|
||||||
contentType: file.type,
|
contentType: file.type,
|
||||||
})
|
})
|
||||||
@ -68,7 +68,7 @@ const Uploader = ({
|
|||||||
removeFingerprintOnSuccess: true,
|
removeFingerprintOnSuccess: true,
|
||||||
})
|
})
|
||||||
.use(Compressor, {
|
.use(Compressor, {
|
||||||
mimeType: type === 'image' ? 'image/webp' : 'application/pdf',
|
mimeType: 'image/webp',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (type === 'image')
|
if (type === 'image')
|
||||||
|
@ -76,7 +76,7 @@ const HomePage = () => (
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="btn btn-square"
|
className="btn btn-square"
|
||||||
>
|
>
|
||||||
{getLogoComponent('github')}
|
{getLogoComponent('gitea')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
Reference in New Issue
Block a user