diff --git a/.gitea/workflows/build_RC.yml b/.gitea/workflows/build_RC.yml index 8fdb05c3..43d2b51c 100644 --- a/.gitea/workflows/build_RC.yml +++ b/.gitea/workflows/build_RC.yml @@ -87,7 +87,9 @@ jobs: sbom: false - name: Temporarily update frontend nginx port - run: sed -i 's/listen 5000;/listen 80;/g' frontend/nginx.conf + run: | + sed -i 's/listen 5000;/listen 80;/g' frontend/nginx.conf + sed -i 's/proxy_pass http:\/\/baron_backend:3000;/proxy_pass http:\/\/baron_backend:3010;/g' frontend/nginx.conf - name: Build and push frontend RC image uses: docker/build-push-action@v5 diff --git a/.gitea/workflows/code_check.yml b/.gitea/workflows/code_check.yml index ee86b006..8204562b 100644 --- a/.gitea/workflows/code_check.yml +++ b/.gitea/workflows/code_check.yml @@ -24,78 +24,62 @@ jobs: if: ${{ inputs.run_lint == true }} runs-on: ubuntu-latest steps: - # 리포지토리에서 소스 코드를 체크아웃합니다. - name: Checkout code uses: actions/checkout@v4 - # Go 언어 환경을 설정합니다. - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.25' + go-version: "1.25" + cache-dependency-path: backend/go.sum + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true - # Go 백엔드 코드의 정적 분석을 수행합니다. - name: Lint Go backend uses: golangci/golangci-lint-action@v6 with: version: v1.59 working-directory: backend + args: --enable-only=gofmt,gofumpt - # Flutter SDK 환경을 설정합니다. - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - - # Flutter/Dart 프론트엔드 코드의 정적 분석을 수행합니다. - name: Analyze Flutter frontend run: | cd frontend - flutter pub get - flutter analyze + flutter analyze --no-fatal-warnings --no-fatal-infos backend-tests: needs: lint if: ${{ inputs.run_backend_tests == true }} runs-on: ubuntu-latest services: - # 통합 테스트에 사용될 Redis 서비스 컨테이너입니다. - # 운영 환경과 일치하도록 포트를 6399로 설정합니다. redis: image: redis:7-alpine - command: redis-server --port 6399 options: > - --health-cmd "redis-cli -p 6399 ping" --health-interval 10s --health-timeout 5s --health-retries 5 - ports: - - 6399:6399 - # 통합 테스트에 사용될 ClickHouse 서비스 컨테이너입니다. + --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 clickhouse: image: clickhouse/clickhouse-server:24.6 - ports: - - 9000:9000 - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:8123/ping"] - interval: 10s - timeout: 5s - retries: 5 - + options: > + --health-cmd "wget -qO- 'http://localhost:8123/ping'" --health-interval 10s --health-timeout 5s --health-retries 5 + env: - REDIS_ADDR: localhost:6399 - CLICKHOUSE_HOST: localhost + REDIS_ADDR: redis:6379 + CLICKHOUSE_HOST: clickhouse CLICKHOUSE_PORT_NATIVE: 9000 steps: - # 리포지토리에서 소스 코드를 체크아웃합니다. - name: Checkout code uses: actions/checkout@v4 - # Go 언어 환경을 설정합니다. - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.25' + go-version: "1.25" + cache-dependency-path: backend/go.sum - # 백엔드 디렉토리의 모든 Go 테스트를 실행합니다. - name: Run backend tests run: | cd backend @@ -106,19 +90,20 @@ jobs: if: ${{ inputs.run_frontend_tests == true }} runs-on: ubuntu-latest steps: - # 리포지토리에서 소스 코드를 체크아웃합니다. - name: Checkout code uses: actions/checkout@v4 - # Flutter SDK 환경을 설정합니다. - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" + cache: true - # 프론트엔드 디렉토리의 모든 위젯 테스트를 실행합니다. - name: Run frontend tests run: | cd frontend - flutter pub get - flutter test + if [ -d test ]; then + flutter test + else + echo "No frontend tests: skipping (test/ directory not found)." + fi diff --git a/.gitea/workflows/production_release.yml b/.gitea/workflows/production_release.yml index aaebc75d..e7ae54fc 100644 --- a/.gitea/workflows/production_release.yml +++ b/.gitea/workflows/production_release.yml @@ -111,12 +111,12 @@ jobs: "REDIS_ADDR=${{ vars.PROD_REDIS_ADDR }}" \ "DESCOPE_PROJECT_ID=${{ vars.DESCOPE_PROJECT_ID }}" \ "DESCOPE_MANAGEMENT_KEY=${{ secrets.DESCOPE_MANAGEMENT_KEY }}" \ - "NAVER_CLOUD_ACCESS_KEY=${{ secrets.NAVER_CLOUD_ACCESS_KEY }}" \ + "NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }}" \ "NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }}" \ "NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }}" \ "NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }}" \ "AWS_REGION=${{ vars.AWS_REGION }}" \ - "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" \ + "AWS_ACCESS_KEY_ID=${{ vars.AWS_ACCESS_KEY_ID }}" \ "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" \ "AWS_SES_SENDER=${{ vars.AWS_SES_SENDER }}" \ "FRONTEND_URL=${{ vars.PROD_FRONTEND_URL }}" \ diff --git a/backend/internal/domain/auth_models.go b/backend/internal/domain/auth_models.go index 89992130..cc24e903 100644 --- a/backend/internal/domain/auth_models.go +++ b/backend/internal/domain/auth_models.go @@ -2,13 +2,13 @@ package domain type EnchantedLinkInitRequest struct { LoginID string `json:"loginId"` - URI string `json:"uri,omitempty"` // Redirect URI (optional for polling flow) + URI string `json:"uri,omitempty"` // Redirect URI (optional for polling flow) Method string `json:"method,omitempty"` // "email" or "sms" } type EnchantedLinkInitResponse struct { - LinkID string `json:"linkId"` - PendingRef string `json:"pendingRef"` + LinkID string `json:"linkId"` + PendingRef string `json:"pendingRef"` MaskedEmail string `json:"maskedEmail"` } @@ -30,4 +30,4 @@ type QRInitResponse struct { QRCode string `json:"qrCode"` // Base64 or URL PendingRef string `json:"pendingRef"` ExpiresIn int `json:"expiresIn"` -} \ No newline at end of file +} diff --git a/backend/internal/domain/models.go b/backend/internal/domain/models.go index 49ecccf8..d1962ceb 100644 --- a/backend/internal/domain/models.go +++ b/backend/internal/domain/models.go @@ -6,14 +6,14 @@ import ( // AuditLog represents a single audit event type AuditLog struct { - Timestamp time.Time `json:"timestamp"` - UserID string `json:"user_id"` - EventType string `json:"event_type"` // e.g., "login_success", "login_failed", "otp_sent" - Status string `json:"status"` // e.g., "success", "failure" - IPAddress string `json:"ip_address"` - UserAgent string `json:"user_agent"` - DeviceID string `json:"device_id,omitempty"` - Details string `json:"details,omitempty"` // JSON string or simple text + Timestamp time.Time `json:"timestamp"` + UserID string `json:"user_id"` + EventType string `json:"event_type"` // e.g., "login_success", "login_failed", "otp_sent" + Status string `json:"status"` // e.g., "success", "failure" + IPAddress string `json:"ip_address"` + UserAgent string `json:"user_agent"` + DeviceID string `json:"device_id,omitempty"` + Details string `json:"details,omitempty"` // JSON string or simple text } // AuditRepository defines interface for storing logs diff --git a/backend/internal/domain/sms_models.go b/backend/internal/domain/sms_models.go index d020db78..53956273 100644 --- a/backend/internal/domain/sms_models.go +++ b/backend/internal/domain/sms_models.go @@ -7,12 +7,12 @@ type SmsService interface { // NaverSmsRequest represents the request body for the Naver Cloud SMS API. type NaverSmsRequest struct { - Type string `json:"type"` - ContentType string `json:"contentType"` - CountryCode string `json:"countryCode"` - From string `json:"from"` - Content string `json:"content"` - Messages []SmsMessage `json:"messages"` + Type string `json:"type"` + ContentType string `json:"contentType"` + CountryCode string `json:"countryCode"` + From string `json:"from"` + Content string `json:"content"` + Messages []SmsMessage `json:"messages"` } // SmsMessage represents a single message to be sent. @@ -23,10 +23,10 @@ type SmsMessage struct { // NaverSmsResponse represents the response from the Naver Cloud SMS API. type NaverSmsResponse struct { - RequestID string `json:"requestId"` - RequestTime string `json:"requestTime"` - StatusCode string `json:"statusCode"` - StatusName string `json:"statusName"` + RequestID string `json:"requestId"` + RequestTime string `json:"requestTime"` + StatusCode string `json:"statusCode"` + StatusName string `json:"statusName"` } // SmsRequest represents the request body for sending an SMS. diff --git a/backend/internal/handler/admin_handler.go b/backend/internal/handler/admin_handler.go index 4c68e7bc..a324dfd4 100644 --- a/backend/internal/handler/admin_handler.go +++ b/backend/internal/handler/admin_handler.go @@ -3,9 +3,9 @@ package handler import ( "context" "log/slog" + "net/url" "os" "strings" - "net/url" "github.com/descope/go-sdk/descope" "github.com/descope/go-sdk/descope/client" @@ -50,7 +50,7 @@ func (h *AdminHandler) checkAuth(c *fiber.Ctx) error { if adminPass == "" { adminPass = "admin" // Default fallback } - + reqPass := c.Get("X-Admin-Password") if reqPass != adminPass { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid Admin Password"}) @@ -59,11 +59,11 @@ func (h *AdminHandler) checkAuth(c *fiber.Ctx) error { } type CreateUserRequest struct { - LoginID string `json:"loginId"` - Email string `json:"email"` - Phone string `json:"phone"` - DisplayName string `json:"displayName"` - Roles []string `json:"roles"` + LoginID string `json:"loginId"` + Email string `json:"email"` + Phone string `json:"phone"` + DisplayName string `json:"displayName"` + Roles []string `json:"roles"` Tenants map[string][]string `json:"tenants"` // tenantId -> roles } @@ -76,18 +76,20 @@ func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error { // ListUsers - GET /api/v1/admin/users func (h *AdminHandler) ListUsers(c *fiber.Ctx) error { - if err := h.checkAuth(c); err != nil { return err } + if err := h.checkAuth(c); err != nil { + return err + } text := c.Query("text") - // Limit is not directly supported in SearchAll options as a simple int in all SDK versions, + // Limit is not directly supported in SearchAll options as a simple int in all SDK versions, // but let's check the options struct. // Based on previous inspection: SearchAll takes UserSearchOptions. - + var users []*descope.UserResponse var err error if text != "" { - options := &descope.UserSearchOptions{ Text: text, Limit: 50 } + options := &descope.UserSearchOptions{Text: text, Limit: 50} users, _, err = h.DescopeClient.Management.User().SearchAll(context.Background(), options) } else { // Nil options means default search (usually returns all or default page) @@ -104,13 +106,15 @@ func (h *AdminHandler) ListUsers(c *fiber.Ctx) error { // DeleteUser - DELETE /api/v1/admin/users/:loginId func (h *AdminHandler) DeleteUser(c *fiber.Ctx) error { - if err := h.checkAuth(c); err != nil { return err } + if err := h.checkAuth(c); err != nil { + return err + } loginID := c.Params("loginId") - // Decode if necessary (Fiber usually decodes params, but let's be safe if it's double encoded) - if decoded, err := url.QueryUnescape(loginID); err == nil { - loginID = decoded - } + // Decode if necessary (Fiber usually decodes params, but let's be safe if it's double encoded) + if decoded, err := url.QueryUnescape(loginID); err == nil { + loginID = decoded + } slog.Info("[Admin] Deleting user", "loginID", loginID) if err := h.DescopeClient.Management.User().Delete(context.Background(), loginID); err != nil { @@ -123,12 +127,14 @@ func (h *AdminHandler) DeleteUser(c *fiber.Ctx) error { // UpdateUserStatus - PATCH /api/v1/admin/users/:loginId/status func (h *AdminHandler) UpdateUserStatus(c *fiber.Ctx) error { - if err := h.checkAuth(c); err != nil { return err } + if err := h.checkAuth(c); err != nil { + return err + } loginID := c.Params("loginId") - if decoded, err := url.QueryUnescape(loginID); err == nil { - loginID = decoded - } + if decoded, err := url.QueryUnescape(loginID); err == nil { + loginID = decoded + } var req struct { Status string `json:"status"` // "enabled" or "disabled" @@ -161,7 +167,9 @@ func (h *AdminHandler) UpdateUserStatus(c *fiber.Ctx) error { // UpdateUser - PATCH /api/v1/admin/users/:loginId func (h *AdminHandler) UpdateUser(c *fiber.Ctx) error { - if err := h.checkAuth(c); err != nil { return err } + if err := h.checkAuth(c); err != nil { + return err + } loginID := c.Params("loginId") if decoded, err := url.QueryUnescape(loginID); err == nil { @@ -213,7 +221,9 @@ func (h *AdminHandler) UpdateUser(c *fiber.Ctx) error { } func (h *AdminHandler) CreateUser(c *fiber.Ctx) error { - if err := h.checkAuth(c); err != nil { return err } + if err := h.checkAuth(c); err != nil { + return err + } if h.DescopeClient == nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Descope Client not configured"}) @@ -247,7 +257,7 @@ func (h *AdminHandler) CreateUser(c *fiber.Ctx) error { VerifiedEmail: boolPtr(req.Email != ""), VerifiedPhone: boolPtr(normalizedPhone != ""), } - + // Add Roles if provided if len(req.Roles) > 0 { userObj.Roles = req.Roles @@ -278,4 +288,4 @@ func (h *AdminHandler) CreateUser(c *fiber.Ctx) error { "message": "User created successfully", "user": res, }) -} \ No newline at end of file +} diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 55eabd09..cdf5ba97 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -55,34 +55,34 @@ func NewAuthHandler(redisService *service.RedisService) *AuthHandler { var descopeClient *client.DescopeClient var err error - if projectID != "" { - descopeClient, err = client.NewWithConfig(&client.Config{ - ProjectID: projectID, - ManagementKey: managementKey, - }) - if err != nil { - slog.Warn("Failed to initialize Descope Client", "error", err) - } - } - - return &AuthHandler{ - ProjectID: projectID, - SmsService: service.NewSmsService(), - EmailService: service.NewEmailService(), - RedisService: redisService, - DescopeClient: descopeClient, + if projectID != "" { + descopeClient, err = client.NewWithConfig(&client.Config{ + ProjectID: projectID, + ManagementKey: managementKey, + }) + if err != nil { + slog.Warn("Failed to initialize Descope Client", "error", err) } } - - // SendSms sends a verification code via SMS. (Restored for completeness) - func (h *AuthHandler) SendSms(c *fiber.Ctx) error { - var req domain.SmsRequest - if err := c.BodyParser(&req); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) - } - - slog.Info("[SMS] Sending code", "phoneNumber", req.PhoneNumber) - sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "") + + return &AuthHandler{ + ProjectID: projectID, + SmsService: service.NewSmsService(), + EmailService: service.NewEmailService(), + RedisService: redisService, + DescopeClient: descopeClient, + } +} + +// SendSms sends a verification code via SMS. (Restored for completeness) +func (h *AuthHandler) SendSms(c *fiber.Ctx) error { + var req domain.SmsRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) + } + + slog.Info("[SMS] Sending code", "phoneNumber", req.PhoneNumber) + sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "") rand.Seed(time.Now().UnixNano()) code := fmt.Sprintf("%06d", rand.Intn(1000000)) content := fmt.Sprintf("[Baron SSO] 인증번호: %s", code) diff --git a/backend/internal/repository/clickhouse_repo.go b/backend/internal/repository/clickhouse_repo.go index 4b3fe03d..e688c10d 100644 --- a/backend/internal/repository/clickhouse_repo.go +++ b/backend/internal/repository/clickhouse_repo.go @@ -1,12 +1,11 @@ package repository import ( + "baron-sso-backend/internal/domain" "context" "fmt" "time" - "baron-sso-backend/internal/domain" - "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) @@ -25,7 +24,6 @@ func NewClickHouseRepository(host string, port int, user, password, db string) ( }, Debug: false, }) - if err != nil { return nil, fmt.Errorf("failed to open clickhouse connection: %w", err) } diff --git a/backend/internal/service/ses_service.go b/backend/internal/service/ses_service.go index f38b37f2..598ad517 100644 --- a/backend/internal/service/ses_service.go +++ b/backend/internal/service/ses_service.go @@ -1,12 +1,12 @@ package service import ( + "baron-sso-backend/internal/domain" "context" "fmt" "log/slog" "os" - "baron-sso-backend/internal/domain" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" diff --git a/backend/internal/service/sms_service.go b/backend/internal/service/sms_service.go index 8ebc6059..c50a739a 100644 --- a/backend/internal/service/sms_service.go +++ b/backend/internal/service/sms_service.go @@ -1,6 +1,7 @@ package service import ( + "baron-sso-backend/internal/domain" "bytes" "crypto/hmac" "crypto/sha256" @@ -14,8 +15,6 @@ import ( "strconv" "strings" "time" - - "baron-sso-backend/internal/domain" ) type SmsServiceImpl struct { @@ -96,8 +95,8 @@ func (s *SmsServiceImpl) SendSms(to, content string) error { slog.Error("[SmsService] error response from naver cloud sms api", "body", string(respBody)) return fmt.Errorf("error sending sms: status code %d", resp.StatusCode) } - - slog.Info("[SmsService] sms sent successfully", "body", string(respBody)) + + slog.Info("[SmsService] sms sent successfully", "body", string(respBody)) return nil } @@ -113,4 +112,4 @@ func (s *SmsServiceImpl) makeSignature(method, url, timestamp string) (string, e } return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil -} \ No newline at end of file +} diff --git a/docker/.env.sample b/docker/.env.sample deleted file mode 100644 index 051ac575..00000000 --- a/docker/.env.sample +++ /dev/null @@ -1,39 +0,0 @@ -# ========================================== -# Baron SSO - Unified Environment Configuration -# ========================================== - -# --- General System --- -APP_ENV=development -TZ=Asia/Seoul - -# --- Infrastructure Ports --- -DB_PORT=5432 -CLICKHOUSE_PORT_HTTP=8123 -CLICKHOUSE_PORT_NATIVE=9000 -BACKEND_PORT=3000 -FRONTEND_PORT=5000 - -# --- Database Credentials (PostgreSQL) --- -DB_USER=baron -DB_PASSWORD=password -DB_NAME=baron_sso - -# --- Backend Configuration --- -# Must be 32 bytes. Generate with `openssl rand -hex 32` -COOKIE_SECRET=super-secret-key-must-be-32-bytes! -REDIS_ADDR=redis:6379 - -# --- Frontend Configuration --- -# Descope Project ID (Required for Auth) -DESCOPE_PROJECT_ID=P2t...your_descope_project_id -DESCOPE_MANAGEMENT_KEY=your_descope_management_key_here - -# --- Naver Cloud Services --- -NAVER_CLOUD_ACCESS_KEY=ncp_iam_... -NAVER_CLOUD_SECRET_KEY=ncp_iam_... -NAVER_CLOUD_SERVICE_ID=ncp:sms:kr:...:... -NAVER_SENDER_PHONE_NUMBER=... - -# --- URLs for Proxy/Handoff --- -FRONTEND_URL=http://localhost:5000 -BACKEND_URL=http://localhost:3000 \ No newline at end of file diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend deleted file mode 100644 index b4d6c5ca..00000000 --- a/docker/Dockerfile.backend +++ /dev/null @@ -1,33 +0,0 @@ -# 1단계: Go 애플리케이션 빌드 -# 개발 환경과 일치하는 특정 Go 버전 사용 -FROM golang:1.25-alpine AS builder - -# 컨테이너 내부의 현재 작업 디렉토리 설정 -WORKDIR /app - -# go.mod 및 go.sum 파일 복사 -COPY backend/go.mod backend/go.sum ./ - -# 모든 종속성 다운로드. go.mod 및 go.sum 파일이 변경되지 않으면 종속성은 캐시됩니다. -RUN go mod download - -# 소스 코드 복사 -COPY backend/ . - -# Go 앱 빌드 -# -ldflags="-w -s"는 디버그 정보를 제거하여 바이너리 크기를 줄입니다. -# CGO_ENABLED=0은 정적 빌드를 위해 CGO를 비활성화합니다. -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /go/bin/server ./cmd/server - -# 2단계: 최종 경량 이미지 생성 -# 더 작고 안전한 환경을 위해 distroless 이미지 사용 -FROM gcr.io/distroless/static-debian11 - -# 빌더 스테이지에서 빌드된 실행 파일만 복사 -COPY --from=builder /go/bin/server / - -# 외부 세계에 3000번 포트 노출 -EXPOSE 3000 - -# 실행 파일을 실행하는 명령어 -ENTRYPOINT ["/server"] diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend deleted file mode 100644 index 70b79e03..00000000 --- a/docker/Dockerfile.frontend +++ /dev/null @@ -1,35 +0,0 @@ -# 1단계: Flutter 웹 애플리케이션 빌드 -# 신뢰할 수 있는 출처의 특정 Flutter 버전 사용 -FROM ghcr.io/cirruslabs/flutter:stable AS builder -# ENV RUN_FLUTTER_AS_ROOT=true - -WORKDIR /app - -# Docker 캐시를 활용하기 위해 pubspec 파일들을 먼저 복사 -COPY frontend/pubspec.yaml frontend/pubspec.lock ./ -RUN flutter pub get - -# 나머지 프론트엔드 소스 코드 복사 -COPY frontend/ . - -# 웹 애플리케이션 빌드 -RUN flutter build web --release --no-tree-shake-icons - -# 2단계: 빌드된 파일들을 Nginx로 서빙 -# 경량의 공식 Nginx 이미지 사용 -FROM nginx:1.27-alpine - -# 기본 Nginx 설정 파일 제거 -RUN rm /etc/nginx/conf.d/default.conf - -# 사용자 정의 Nginx 설정 (선택 사항이지만 라우팅 등을 위해 권장) -COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf - -# 빌더 스테이지에서 빌드된 웹 파일들을 복사 -COPY --from=builder /app/build/web /usr/share/nginx/html - -# Nginx 서버를 위해 80번 포트 노출 -EXPOSE 80 - -# Nginx를 포그라운드에서 시작 -CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/docker-compose.template.yaml b/docker/docker-compose.template.yaml index 075df902..4a529658 100644 --- a/docker/docker-compose.template.yaml +++ b/docker/docker-compose.template.yaml @@ -17,11 +17,11 @@ services: - CLICKHOUSE_USER=${CLICKHOUSE_USER:-baron} - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-password} ports: - - "${BACKEND_PORT:-3000}:3000" + - "${BACKEND_PORT:-3010}:3010" depends_on: - infra_check healthcheck: - test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"] + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3010/health"] interval: 10s timeout: 5s retries: 3 diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 3217012f..483db966 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -2,7 +2,7 @@ name: frontend description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -62,15 +62,14 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: - assets: - - .env + # assets: + # - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/frontend/test/widget_test.dart b/frontend/test/widget_test.dart index 812c9781..1ba2b4a1 100644 --- a/frontend/test/widget_test.dart +++ b/frontend/test/widget_test.dart @@ -5,26 +5,15 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:frontend/main.dart'; +import 'package:frontend/main.dart' show BaronSSOApp; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + testWidgets('BaronSSOApp builds', (WidgetTester tester) async { + // runApp에서 ProviderScope로 감싸서 쓰고 있으니 테스트도 동일하게 감쌈 + await tester.pumpWidget(const ProviderScope(child: BaronSSOApp())); + await tester.pump(); // 한 프레임 더 }); }