diff --git a/.env.test b/.env.test index 4ff489b..047894e 100644 --- a/.env.test +++ b/.env.test @@ -16,8 +16,8 @@ TYPESENSE_API_KEY=ts_dev_key_change_me # MinIO MINIO_ENDPOINT=localhost MINIO_PORT=9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin_secret +MINIO_ACCESS_KEY=test_minio_user +MINIO_SECRET_KEY=test_minio_secret_key_32chars!! MINIO_BUCKET=goodgo-uploads # Auth (deterministic secrets for test reproducibility) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 36278d9..8919e6f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -60,8 +60,8 @@ jobs: ports: - 9000:9000 env: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin_secret + MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }} + MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }} options: >- --health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1" --health-interval 10s @@ -77,8 +77,8 @@ jobs: TYPESENSE_API_KEY: ts_ci_key MINIO_ENDPOINT: localhost MINIO_PORT: 9000 - MINIO_ACCESS_KEY: minioadmin - MINIO_SECRET_KEY: minioadmin_secret + MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }} + MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }} MINIO_BUCKET: goodgo-uploads NODE_ENV: test JWT_SECRET: e2e-test-jwt-secret-key diff --git a/apps/api/package.json b/apps/api/package.json index 022747c..be46119 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.1026.0", + "@aws-sdk/s3-request-presigner": "^3.1026.0", "@goodgo/mcp-servers": "workspace:*", "@nestjs/common": "^11.0.0", "@nestjs/core": "^11.0.0", diff --git a/apps/api/src/modules/listings/infrastructure/services/media-storage.service.ts b/apps/api/src/modules/listings/infrastructure/services/media-storage.service.ts index 6495986..5e02677 100644 --- a/apps/api/src/modules/listings/infrastructure/services/media-storage.service.ts +++ b/apps/api/src/modules/listings/infrastructure/services/media-storage.service.ts @@ -7,6 +7,7 @@ import { HeadBucketCommand, CreateBucketCommand, } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { Injectable, type OnModuleInit } from '@nestjs/common'; import { type LoggerService } from '@modules/shared/infrastructure/logger.service'; @@ -15,6 +16,7 @@ export const MEDIA_STORAGE_SERVICE = Symbol('MEDIA_STORAGE_SERVICE'); export interface IMediaStorageService { upload(buffer: Buffer, originalName: string, mimeType: string, folder: string): Promise; delete(fileUrl: string): Promise; + getPresignedUploadUrl(objectKey: string, mimeType: string, expiresInSeconds?: number): Promise; } function requireEnv(key: string): string { @@ -106,6 +108,15 @@ export class MinioMediaStorageService implements IMediaStorageService, OnModuleI } } + async getPresignedUploadUrl(objectKey: string, mimeType: string, expiresInSeconds = 300): Promise { + const command = new PutObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + ContentType: mimeType, + }); + return getSignedUrl(this.s3, command, { expiresIn: expiresInSeconds }); + } + async delete(fileUrl: string): Promise { try { const urlObj = new URL(fileUrl); diff --git a/docker-compose.yml b/docker-compose.yml index d71c007..dab1981 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,8 +68,8 @@ services: - '${MINIO_CONSOLE_PORT:-9001}:9001' command: server /data --console-address ":9001" environment: - MINIO_ROOT_USER: ${MINIO_USER:-minioadmin} - MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD:-minioadmin_secret} + MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY is required} + MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required} volumes: - minio_data:/data healthcheck: diff --git a/docs/dev-environment.md b/docs/dev-environment.md index 2848cca..d53f1d0 100644 --- a/docs/dev-environment.md +++ b/docs/dev-environment.md @@ -130,7 +130,7 @@ curl http://localhost:8108/health ### MinIO - **API**: `http://localhost:9000` -- **Console**: `http://localhost:9001` (login: `minioadmin` / `minioadmin_secret`) +- **Console**: `http://localhost:9001` (login with `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` from your `.env`) ### AI Services diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a228bf3..0cc2d00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.1026.0 version: 3.1026.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.1026.0 + version: 3.1026.0 '@goodgo/mcp-servers': specifier: workspace:* version: link:../../libs/mcp-servers @@ -485,6 +488,10 @@ packages: resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==} engines: {node: '>=20.0.0'} + '@aws-sdk/s3-request-presigner@3.1026.0': + resolution: {integrity: sha512-PBVt/zb4YsJMcyB/HbGmID4RP00dTkdQGkNQiw1i6oXQ/U8hnPEI8+IvTKR4+5YEQ8Cq4QmtIV0mzv070L+oOg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.16': resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==} engines: {node: '>=20.0.0'} @@ -505,6 +512,10 @@ packages: resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==} engines: {node: '>=20.0.0'} + '@aws-sdk/util-format-url@3.972.9': + resolution: {integrity: sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.965.5': resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} engines: {node: '>=20.0.0'} @@ -6273,6 +6284,17 @@ snapshots: '@smithy/types': 4.14.0 tslib: 2.8.1 + '@aws-sdk/s3-request-presigner@3.1026.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-format-url': 3.972.9 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.16': dependencies: '@aws-sdk/middleware-sdk-s3': 3.972.28 @@ -6311,6 +6333,13 @@ snapshots: '@smithy/util-endpoints': 3.3.4 tslib: 2.8.1 + '@aws-sdk/util-format-url@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.965.5': dependencies: tslib: 2.8.1