diff --git a/.dockerignore b/.dockerignore index e07e90e6a9e6..bc31235f761c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,6 @@ node_modules npm-debug.log .next .git -scripts docs .github *.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 789d4bc134ba..b312728655fd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,9 +6,10 @@ - [ ] ๐Ÿ› fix - [ ] โ™ป๏ธ refactor - [ ] ๐Ÿ’„ style -- [ ] ๐Ÿ”จ chore +- [ ] ๐Ÿ‘ท build - [ ] โšก๏ธ perf - [ ] ๐Ÿ“ docs +- [ ] ๐Ÿ”จ chore #### ๐Ÿ”€ ๅ˜ๆ›ด่ฏดๆ˜Ž | Description of Change diff --git a/.github/workflows/docker-database.yml b/.github/workflows/docker-database.yml new file mode 100644 index 000000000000..5d82c112fbaf --- /dev/null +++ b/.github/workflows/docker-database.yml @@ -0,0 +1,46 @@ +name: Publish Database Docker Image + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_REGISTRY_USER }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: lobehub/lobe-chat-database + tags: | + type=raw,value=latest + type=ref,event=tag + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.database # ๆŒ‡ๅฎšไฝฟ็”จ Dockerfile.database ๆ–‡ไปถ + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile.database b/Dockerfile.database new file mode 100644 index 000000000000..f16bb9fffef6 --- /dev/null +++ b/Dockerfile.database @@ -0,0 +1,170 @@ +## Base image for all the stages +FROM node:20-alpine AS base + +RUN \ + # Add user nextjs to run the app + addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +## Builder image, install all the dependencies and build the app +FROM base AS builder + +ARG USE_NPM_CN_MIRROR + +ENV KEY_VAULTS_SECRET="use-for-build" \ + NEXT_PUBLIC_SERVICE_MODE="server" \ + DATABASE_DRIVER="node" \ + DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" + +# Sentry +ENV NEXT_PUBLIC_SENTRY_DSN="" \ + SENTRY_ORG="" \ + SENTRY_PROJECT="" + +# Posthog +ENV NEXT_PUBLIC_ANALYTICS_POSTHOG="" \ + NEXT_PUBLIC_POSTHOG_HOST="" \ + NEXT_PUBLIC_POSTHOG_KEY="" + +# Umami +ENV NEXT_PUBLIC_ANALYTICS_UMAMI="" \ + NEXT_PUBLIC_UMAMI_SCRIPT_URL="" \ + NEXT_PUBLIC_UMAMI_WEBSITE_ID="" + +# Node +ENV NODE_OPTIONS="--max-old-space-size=8192" + +WORKDIR /app + +COPY package.json ./ +COPY .npmrc ./ + +RUN \ + # If you want to build docker in China, build with --build-arg USE_NPM_CN_MIRROR=true + if [ "${USE_NPM_CN_MIRROR:-false}" = "true" ]; then \ + export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli"; \ + npm config set registry "https://registry.npmmirror.com/"; \ + fi \ + # Set the registry for corepack + && export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\/$//') \ + # Enable corepack + && corepack enable \ + # Use pnpm for corepack + && corepack use pnpm \ + # Install the dependencies + && pnpm i \ + # Add sharp and db migration dependencies + && mkdir -p /deps \ + && pnpm add sharp pg drizzle-orm --prefix /deps + +COPY . . + +# run build standalone for docker version +RUN npm run build:docker + +## Application image, copy all the files for production +FROM scratch AS app + +COPY --from=builder /app/public /app/public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder /app/.next/standalone /app/ +COPY --from=builder /app/.next/static /app/.next/static + +# copy dependencies +COPY --from=builder /deps/node_modules/.pnpm /app/node_modules/.pnpm +COPY --from=builder /deps/node_modules/pg /app/node_modules/pg +COPY --from=builder /deps/node_modules/drizzle-orm /app/node_modules/drizzle-orm + +# Copy database migrations +COPY --from=builder /app/src/database/server/migrations /app/migrations +COPY --from=builder /app/scripts/migrateServerDB/docker.cjs /app/docker.cjs + +## Production image, copy all the files and run next +FROM base + +# Copy all the files from app, set the correct permission for prerender cache +COPY --from=app --chown=nextjs:nodejs /app /app + +ENV NODE_ENV="production" + +# set hostname to localhost +ENV HOSTNAME="0.0.0.0" \ + PORT="3210" + +# General Variables +ENV API_KEY_SELECT_MODE="" \ + FEATURE_FLAGS="" + +# Database +ENV KEY_VAULTS_SECRET="" \ +DATABASE_DRIVER="node" \ +DATABASE_URL="" + +# Next Auth +ENV NEXT_AUTH_SECRET="" \ +ACCESS_CODE="" \ +NEXTAUTH_URL="" \ +NEXT_AUTH_SSO_PROVIDERS="" + +# S3 +ENV S3_ACCESS_KEY_ID="" \ + S3_SECRET_ACCESS_KEY="" \ + NEXT_PUBLIC_S3_DOMAIN="" \ + S3_ENDPOINT="" \ + S3_BUCKET="" + +# Model Variables +ENV \ + # Ai360 + AI360_API_KEY="" \ + # Anthropic + ANTHROPIC_API_KEY="" ANTHROPIC_PROXY_URL="" \ + # Amazon Bedrock + AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" AWS_REGION="" \ + # Azure OpenAI + AZURE_API_KEY="" AZURE_API_VERSION="" AZURE_ENDPOINT="" AZURE_MODEL_LIST="" \ + # Baichuan + BAICHUAN_API_KEY="" \ + # DeepSeek + DEEPSEEK_API_KEY="" \ + # Google + GOOGLE_API_KEY="" GOOGLE_PROXY_URL="" \ + # Groq + GROQ_API_KEY="" GROQ_PROXY_URL="" \ + # Minimax + MINIMAX_API_KEY="" \ + # Mistral + MISTRAL_API_KEY="" \ + # Moonshot + MOONSHOT_API_KEY="" MOONSHOT_PROXY_URL="" \ + # Novita + NOVITA_API_KEY="" \ + # Ollama + OLLAMA_MODEL_LIST="" OLLAMA_PROXY_URL="" \ + # OpenAI + OPENAI_API_KEY="" OPENAI_MODEL_LIST="" OPENAI_PROXY_URL="" \ + # OpenRouter + OPENROUTER_API_KEY="" OPENROUTER_MODEL_LIST="" \ + # Perplexity + PERPLEXITY_API_KEY="" PERPLEXITY_PROXY_URL="" \ + # Qwen + QWEN_API_KEY="" \ + # Stepfun + STEPFUN_API_KEY="" \ + # Taichu + TAICHU_API_KEY="" \ + # TogetherAI + TOGETHERAI_API_KEY="" TOGETHERAI_MODEL_LIST="" \ + # 01.AI + ZEROONE_API_KEY="" \ + # Zhipu + ZHIPU_API_KEY="" + +USER nextjs + +EXPOSE 3210/tcp + +# run migration , then run app +CMD ["sh", "-c", "node /app/docker.cjs && node /app/server.js"] diff --git a/scripts/migrateServerDB/docker.cjs b/scripts/migrateServerDB/docker.cjs new file mode 100644 index 000000000000..cc39a651af6c --- /dev/null +++ b/scripts/migrateServerDB/docker.cjs @@ -0,0 +1,34 @@ +const { join } = require('node:path'); +const { Pool } = require('pg'); +const { drizzle } = require('drizzle-orm/node-postgres'); +const migrator = require('drizzle-orm/node-postgres/migrator'); + +if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL is not set, please set it in your environment variables.'); +} + +const client = new Pool({ connectionString: process.env.DATABASE_URL }); + +const db = drizzle(client); + +const runMigrations = async () => { + console.log('[Database] Start to migration...'); + await migrator.migrate(db, { + migrationsFolder: join(__dirname, './migrations'), + }); + + console.log('โœ… database migration pass.'); + console.log('-------------------------------------'); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(0); +}; + +// eslint-disable-next-line unicorn/prefer-top-level-await +runMigrations().catch((err) => { + console.error( + 'โŒ Database migrate failed. Please check your database is valid and DATABASE_URL is set correctly. The error detail is below:', + ); + console.error(err); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); +}); diff --git a/src/libs/next-auth/adapter/index.ts b/src/libs/next-auth/adapter/index.ts index 1b9d4c7f7765..129da498f185 100644 --- a/src/libs/next-auth/adapter/index.ts +++ b/src/libs/next-auth/adapter/index.ts @@ -145,13 +145,9 @@ export function LobeNextAuthDbAdapter(serverDB: NeonDatabase): Ad }, async getUser(id): Promise { - try { - const lobeUser = await UserModel.findById(id); - if (!lobeUser) return null; - return mapLobeUserToAdapterUser(lobeUser); - } catch { - return null; - } + const lobeUser = await UserModel.findById(id); + if (!lobeUser) return null; + return mapLobeUserToAdapterUser(lobeUser); }, async getUserByAccount(account): Promise {