first commit
CI / typecheck (push) Successful in 1m8s
CI / format (push) Failing after 1m6s
CI / lint (push) Failing after 49s
CI / test (push) Failing after 1m8s

This commit is contained in:
SDI
2026-07-15 18:05:12 +09:00
commit 12e4f17b62
4633 changed files with 817125 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
# The web Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker.
# Make sure you update this Dockerfile, the Dockerfile in the web workspace and copy that over to Dockerfile in the docs.
FROM node:24.14.1-alpine AS base
FROM base AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
RUN npm install -g turbo
COPY . .
RUN turbo prune --scope=api --docker
# Add lockfile and package.json's of isolated subworkspace
FROM base AS installer
RUN apk add --no-cache libc6-compat
WORKDIR /app
RUN npm install -g corepack@latest
RUN corepack enable
# First install dependencies (as they change less often)
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
RUN pnpm install -w source-map-support
# Build the project and its dependencies
COPY --from=builder /app/out/full/ .
COPY turbo.json ./
COPY .turbo/ ./.turbo/
RUN pnpm dlx turbo run build --filter=api...
FROM base AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nestjs
USER nestjs
COPY --from=installer --chown=nestjs:nodejs /app .
CMD ["node", "-r", "source-map-support/register", "apps/api/dist/main.js"]
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
if [[ ${1} == "help" || "$#" -lt 2 ]]; then
echo "Usage: sh ${0} APP VER"
echo "APP APP of docker image"
echo " ex. api, web, docs"
echo "VER Version of docker image"
echo " ex. 1.0.0, 1.1.0, ..."
exit;
fi
APP=${1}
VER=${2}
IMAGE="user-feedback/${APP}"
TAG="${IMAGE}:${VER}"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
DOCKER_DIR="$( cd "${DIR}" >/dev/null 2>&1 && pwd )"
APP_DIR="$( cd "${DIR}/../" >/dev/null 2>&1 && pwd )"
DOCKER_FILE=${DOCKER_DIR}/${1}.dockerfile
echo ""
echo "DOCKER_FILE:${DOCKER_FILE}"
echo "TAG:${TAG}"
echo ""
echo "docker image build start - ${TAG}"
docker build -t ${TAG} -f ${DOCKER_FILE} ${APP_DIR}
if [[ $? -ne 0 ]]; then
echo "docker image build failed - ${TAG}"
exit -1;
fi
echo "docker image build done - ${TAG}"
+21
View File
@@ -0,0 +1,21 @@
group "default" {
targets = ["api", "web"]
}
target "api" {
context = "."
dockerfile = "./docker/api.dockerfile"
tags = ["line/abc-user-feedback-api:latest"]
platforms = ["linux/amd64", "linux/arm64/v8"]
cache-from = ["type=gha"]
cache-to = ["type=gha,mode=max"]
}
target "web" {
context = "."
dockerfile = "./docker/web.dockerfile"
tags = ["line/abc-user-feedback-web:latest"]
platforms = ["linux/amd64", "linux/arm64/v8"]
cache-from = ["type=gha"]
cache-to = ["type=gha,mode=max"]
}
+52
View File
@@ -0,0 +1,52 @@
name: abc-user-feedback
services:
web:
image: line/abc-user-feedback-web:latest
environment:
- NEXT_PUBLIC_API_BASE_URL=http://localhost:4000
- SUPPORT_API_BASE_URL=http://secretary-api:8010
ports:
- 3002:3000
depends_on:
- api
- secretary-api
restart: unless-stopped
api:
image: line/abc-user-feedback-api:latest
environment:
- JWT_SECRET=jwtsecretjwtsecretjwtsecret
- MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@mysql:3306/userfeedback
- SMTP_HOST=smtp4dev
- SMTP_PORT=25
- SMTP_SENDER=user@feedback.com
- OPENSEARCH_USE=true
- OPENSEARCH_NODE=http://opensearch-node:9200
ports:
- 4000:4000
restart: unless-stopped
secretary-api:
build:
context: ..
dockerfile: docker/secretary-api.dockerfile
environment:
- APP_NAME=secretary-api
- APP_ENV=local
- APP_HOST=0.0.0.0
- APP_PORT=8010
- DATABASE_URL=mysql+pymysql://baron_support:baron_support@mysql-secretary:3306/baron_support
- ABC_API_BASE_URL=http://api:4000
- ABC_API_KEY=3B1B08761D2DD0B9C021
- ABC_PROJECT_ID=1
- ABC_CHANNEL_ID=1
- UPLOAD_ROOT_DIR=/app/uploads
ports:
- 8010:8010
depends_on:
- api
- mysql-secretary
volumes:
- ../uploads:/app/uploads
restart: unless-stopped
+38
View File
@@ -0,0 +1,38 @@
version: '3'
services:
web:
hostname: web
build:
context: ..
dockerfile: docker/web.dockerfile
restart: always
ports:
- 3000:3000
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
- NEXT_PUBLIC_API_BASE_URL=http://localhost:4000
depends_on:
- api
api:
hostname: api
build:
context: ..
dockerfile: docker/api.dockerfile
restart: always
ports:
- 4000:4000
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
- JWT_SECRET=jwtsecretjwtsecretjwtsecret
- MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@host.docker.internal:13307/e2e
- SMTP_HOST=localhost
- SMTP_PORT=25
- SMTP_SENDER=abc@feedback.user
- APP_PORT=4000
- AUTO_MIGRATION=true
- MASTER_API_KEY=MASTER_API_KEY
- NODE_OPTIONS="--max_old_space_size=3072"
+115
View File
@@ -0,0 +1,115 @@
name: abc-user-feedback
services:
mysql:
image: mysql:8.0
command:
[
"--default-authentication-plugin=mysql_native_password",
"--collation-server=utf8mb4_bin",
]
environment:
MYSQL_ROOT_PASSWORD: userfeedback
MYSQL_DATABASE: userfeedback
MYSQL_USER: userfeedback
MYSQL_PASSWORD: userfeedback
TZ: UTC
ports:
- 13306:3306
volumes:
- mysql:/var/lib/mysql
restart: unless-stopped
mysql-secretary:
hostname: mysql-secretary
image: mysql:8.0
command:
[
"--default-authentication-plugin=mysql_native_password",
"--collation-server=utf8mb4_bin",
]
environment:
MYSQL_ROOT_PASSWORD: baron_support
MYSQL_DATABASE: baron_support
MYSQL_USER: baron_support
MYSQL_PASSWORD: baron_support
TZ: UTC
ports:
- 13308:3306
volumes:
- mysql-secretary:/var/lib/mysql
restart: unless-stopped
mysql-for-e2e:
hostname: mysql-for-e2e
image: mysql:8.0
command:
[
"--default-authentication-plugin=mysql_native_password",
"--collation-server=utf8mb4_bin",
]
environment:
MYSQL_ROOT_PASSWORD: userfeedback
MYSQL_DATABASE: e2e
MYSQL_USER: userfeedback
MYSQL_PASSWORD: userfeedback
TZ: UTC
ports:
- 13307:3306
volumes:
- mysql-for-e2e:/var/lib/mysql-for-e2e
restart: unless-stopped
smtp4dev:
image: rnwood/smtp4dev:v3
ports:
- 5080:80
- 25:25
- 143:143
volumes:
- smtp4dev:/smtp4dev
restart: unless-stopped
# optional for better performance on searching feedbacks
opensearch-node:
image: opensearchproject/opensearch:2.16.0
restart: always
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
- plugins.security.disabled=true
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=UserFeedback123!@#
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- opensearch:/usr/share/opensearch/data
ports:
- 9200:9200
- 9600:9600
# optional for opensearch
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:2.16.0
restart: always
ports:
- 5601:5601
environment:
- 'OPENSEARCH_HOSTS=["http://opensearch-node:9200"]'
- "DISABLE_SECURITY_DASHBOARDS_PLUGIN=true"
depends_on:
- opensearch-node
volumes:
mysql:
mysql-secretary:
mysql-for-e2e:
smtp4dev:
opensearch:
+52
View File
@@ -0,0 +1,52 @@
name: abc-user-feedback-vector
services:
opensearch-node:
image: opensearchproject/opensearch:2.19.4
restart: unless-stopped
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node
- discovery.type=single-node
- bootstrap.memory_lock=true
- 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m'
- plugins.security.disabled=true
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=UserFeedback123!@#
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- opensearch-data:/usr/share/opensearch/data
ports:
- '9201:9200'
- '9601:9600'
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:2.19.4
restart: unless-stopped
ports:
- '5602:5601'
environment:
- 'OPENSEARCH_HOSTS=["http://opensearch-node:9200"]'
- 'DISABLE_SECURITY_DASHBOARDS_PLUGIN=true'
depends_on:
- opensearch-node
vector:
image: timberio/vector:latest-alpine
command: ['--config', '/etc/vector/vector.yml']
volumes:
- ./vector.yml:/etc/vector/vector.yml:ro
ports:
- '4319:4319' # OTLP HTTP
- '4317:4317' # OTLP gRPC
depends_on:
- opensearch-node
restart: unless-stopped
volumes:
opensearch-data:
+4
View File
@@ -0,0 +1,4 @@
services:
smtp4dev:
ports:
- 5080:80
+132
View File
@@ -0,0 +1,132 @@
name: abc-user-feedback
services:
web:
image: line/abc-user-feedback-web:latest
environment:
- NEXT_PUBLIC_API_BASE_URL=http://localhost:4000
- SUPPORT_API_BASE_URL=http://secretary-api:8010
ports:
- 3002:3000
depends_on:
- api
- secretary-api
restart: unless-stopped
api:
image: line/abc-user-feedback-api:latest
environment:
- JWT_SECRET=jwtsecretjwtsecretjwtsecret
- MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@mysql:3306/userfeedback
- SMTP_HOST=smtp4dev
- SMTP_PORT=25
- SMTP_SENDER=user@feedback.com
# - OPENSEARCH_USE=true
# - OPENSEARCH_NODE=http://opensearch-node:9200
ports:
- 4000:4000
depends_on:
- mysql
restart: unless-stopped
secretary-api:
build:
context: ..
dockerfile: docker/secretary-api.dockerfile
environment:
- APP_NAME=secretary-api
- APP_ENV=local
- APP_HOST=0.0.0.0
- APP_PORT=8010
- DATABASE_URL=mysql+pymysql://baron_support:baron_support@mysql-secretary:3306/baron_support
- ABC_API_BASE_URL=http://api:4000
- ABC_API_KEY=3B1B08761D2DD0B9C021
- ABC_PROJECT_ID=1
- ABC_CHANNEL_ID=1
- UPLOAD_ROOT_DIR=/app/uploads
ports:
- 8010:8010
depends_on:
- api
- mysql-secretary
volumes:
- ../uploads:/app/uploads
restart: unless-stopped
mysql:
image: mysql:8.0
command:
[
'--default-authentication-plugin=mysql_native_password',
'--collation-server=utf8mb4_bin',
]
environment:
MYSQL_ROOT_PASSWORD: userfeedback
MYSQL_DATABASE: userfeedback
MYSQL_USER: userfeedback
MYSQL_PASSWORD: userfeedback
TZ: UTC
ports:
- 13306:3306
volumes:
- mysql:/var/lib/mysql
restart: unless-stopped
mysql-secretary:
hostname: mysql-secretary
image: mysql:8.0
command:
[
'--default-authentication-plugin=mysql_native_password',
'--collation-server=utf8mb4_bin',
]
environment:
MYSQL_ROOT_PASSWORD: baron_support
MYSQL_DATABASE: baron_support
MYSQL_USER: baron_support
MYSQL_PASSWORD: baron_support
TZ: UTC
ports:
- 13308:3306
volumes:
- mysql-secretary:/var/lib/mysql
restart: unless-stopped
smtp4dev:
image: rnwood/smtp4dev:v3
ports:
- 5080:80
- 25:25
- 143:143
volumes:
- smtp4dev:/smtp4dev
restart: unless-stopped
# opensearch-node:
# image: opensearchproject/opensearch:2.16.0
# restart: unless-stopped
# environment:
# - cluster.name=opensearch-cluster
# - node.name=opensearch-node
# - discovery.type=single-node
# - bootstrap.memory_lock=true
# - 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m'
# - plugins.security.disabled=true
# - OPENSEARCH_INITIAL_ADMIN_PASSWORD=UserFeedback123!@#
# ulimits:
# memlock:
# soft: -1
# hard: -1
# nofile:
# soft: 65536
# hard: 65536
# volumes:
# - opensearch:/usr/share/opensearch/data
# ports:
# - 9200:9200
# - 9600:9600
volumes:
mysql:
mysql-secretary:
smtp4dev:
# opensearch:
+46
View File
@@ -0,0 +1,46 @@
FROM node:24.14.1-alpine AS base
# ---- Builder Stage ----
FROM base AS builder
WORKDIR /app
RUN npm install -g turbo
COPY . .
# Only isolate needed parts for docs
RUN turbo prune --scope=docs --docker
# ---- Installer Stage ----
FROM base AS installer
WORKDIR /app
RUN corepack enable
# Copy pruned workspace
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
# Install only necessary dependencies
RUN pnpm install --frozen-lockfile
# Copy full source for build
COPY --from=builder /app/out/full/ .
COPY turbo.json ./
COPY .turbo/ ./.turbo/
COPY .git/ ./.git/
# Build Docusaurus
RUN pnpm dlx turbo run build --filter=docs...
# ---- Runner Stage ----
FROM installer AS runner
EXPOSE 3000
WORKDIR /app/apps/docs
CMD ["pnpm", "start", "--host", "0.0.0.0", "--no-open"]
+21
View File
@@ -0,0 +1,21 @@
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app/apps/secretary-api
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential default-libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
COPY apps/secretary-api/pyproject.toml ./pyproject.toml
COPY apps/secretary-api/README.md ./README.md
COPY apps/secretary-api/alembic.ini ./alembic.ini
COPY apps/secretary-api/alembic ./alembic
COPY apps/secretary-api/app ./app
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir .
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8010"]
+121
View File
@@ -0,0 +1,121 @@
# ────────────────────────────────────────────────
# Vector Pipeline: OTLP Logs → OpenSearch
# ────────────────────────────────────────────────
api:
enabled: true
address: '0.0.0.0:8686'
sources:
otlp_logs:
type: opentelemetry
grpc:
address: '0.0.0.0:4317'
http:
address: '0.0.0.0:4319'
transforms:
build_message:
type: remap
inputs:
- otlp_logs.logs
source: |
# time
if exists(.timestamp) && .timestamp != null {
._time_str = format_timestamp!(.timestamp, "%H:%M:%S%.3f")
} else { ._time_str = "" }
# level
if exists(.severity_text) && .severity_text != null {
._level = upcase(string!(.severity_text))
} else { ._level = "INFO" }
# pid
if exists(.resources."process.pid") && .resources."process.pid" != null {
._pid_str = to_string!(.resources."process.pid")
} else { ._pid_str = "" }
# message
._orig_msg = ""
if exists(.message) && .message != null {
._orig_msg = string!(.message)
}
# attrs json
._json_str = ""
if exists(.attributes) && .attributes != null {
._json_str = encode_json(.attributes)
}
# build
if ._pid_str != "" {
.message = "[" + ._time_str + "] " + ._level + " (" + ._pid_str + "): " + ._orig_msg
} else {
.message = "[" + ._time_str + "] " + ._level + ": " + ._orig_msg
}
if ._json_str != "" && ._json_str != "{}" {
.message = .message + " " + ._json_str
}
# cleanup
del(._time_str)
del(._level)
del(._pid_str)
del(._orig_msg)
del(._json_str)
if exists(.attributes) { del(.attributes) }
if exists(.resources."service.name") {
.service.name = .resources."service.name"
}
if exists(.resources."service.version") {
.service.version = .resources."service.version"
}
if exists(.resources) { del(.resources) }
sinks:
opensearch:
type: elasticsearch
inputs:
- build_message
endpoints:
- '${OPENSEARCH_ENDPOINT:-http://opensearch-node:9200}'
auth:
strategy: basic
user: '${OPENSEARCH_USERNAME:-}'
password: '${OPENSEARCH_PASSWORD:-}'
tls:
verify_certificate: ${OPENSEARCH_TLS_VERIFY:-false}
api_version: v8
opensearch_service_type: managed
bulk:
index: '${OPENSEARCH_INDEX_PREFIX:-logs}-%Y.%m.%d'
action: index
batch:
max_bytes: 10485760 # 10MB
timeout_secs: 5
buffer:
type: memory
max_events: 10000
when_full: block
console_out:
type: console
inputs:
- build_message
encoding:
codec: json
drop_metrics:
type: blackhole
inputs:
- otlp_logs.metrics
print_interval_secs: 0
drop_traces:
type: blackhole
inputs:
- otlp_logs.traces
print_interval_secs: 0
+68
View File
@@ -0,0 +1,68 @@
# This Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker.
# Make sure you update both files!
FROM node:24.14.1-alpine AS base
FROM base AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
RUN npm install -g turbo
COPY . .
RUN turbo prune --scope=web --docker
# Add lockfile and package.json's of isolated subworkspace
FROM base AS installer
RUN apk add --no-cache libc6-compat
RUN apk --no-cache add --virtual .builds-deps build-base python3
WORKDIR /app
RUN npm install -g node-gyp corepack@latest
RUN corepack enable
# First install the dependencies (as they change less often)
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
# Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json ./
COPY .turbo/ ./.turbo/
COPY --from=builder /app/apps/web/.env.build /app/apps/web/.env.production
RUN SKIP_ENV_VALIDATION=true pnpm dlx turbo run build --filter=web...
FROM base AS runner
WORKDIR /app
# install usermod and change node user to 1001
RUN echo http://dl-2.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
RUN apk --no-cache add shadow
RUN groupmod -g 1001 node \
&& usermod -u 1001 -g 1001 node
# Don't run production as root
RUN addgroup --system --gid 3000 nodejs
RUN adduser --system -G nodejs --uid 1000 nextjs -D
USER nextjs
COPY --from=installer /app/apps/web/next-i18next.config.js .
COPY --from=installer /app/apps/web/next.config.js .
COPY --from=installer /app/apps/web/package.json .
COPY --from=installer /app/apps/web/.env.production ./apps/web/.env.production
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/entrypoint.sh ./apps/web/entrypoint.sh
ENTRYPOINT ["apps/web/entrypoint.sh"]
CMD ["node", "apps/web/server.js"]