Skip to content

Commit 52116ef

Browse files
committed
Merge branch 'develop' into test/load-testing
2 parents 55eb4a7 + a4a46f9 commit 52116ef

24 files changed

+1908
-78
lines changed

.env.example

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ===========================================
2+
# ProjectVG API Server - Environment Variables Template
3+
# ===========================================
4+
# Copy this file to .env and fill in your actual values
5+
6+
# Database Configuration
7+
DB_CONNECTION_STRING=Server=host.docker.internal,1433;Database=ProjectVG;User Id=sa;Password=YOUR_DB_PASSWORD;TrustServerCertificate=true;MultipleActiveResultSets=true
8+
DB_PASSWORD=YOUR_DB_PASSWORD
9+
10+
# Redis Configuration
11+
REDIS_CONNECTION_STRING=host.docker.internal:6380
12+
13+
# External Services
14+
LLM_BASE_URL=http://host.docker.internal:7908
15+
MEMORY_BASE_URL=http://host.docker.internal:7912
16+
TTS_BASE_URL=https://supertoneapi.com
17+
TTS_API_KEY=YOUR_TTS_API_KEY
18+
19+
# JWT Configuration (IMPORTANT: Use a secure random key in production)
20+
JWT_SECRET_KEY=YOUR_JWT_SECRET_KEY_MINIMUM_32_CHARACTERS
21+
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
22+
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30
23+
24+
# OAuth2 Configuration
25+
OAUTH2_ENABLED=true
26+
GOOGLE_OAUTH_ENABLED=true
27+
GOOGLE_OAUTH_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
28+
GOOGLE_OAUTH_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
29+
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:7900/auth/oauth2/callback
30+
GOOGLE_OAUTH_AUTO_CREATE_USER=true
31+
GOOGLE_OAUTH_DEFAULT_ROLE=User
32+
33+
# Application Configuration
34+
ASPNETCORE_ENVIRONMENT=Production
35+
36+
# Container Resource Limits
37+
API_CPU_LIMIT=1.0
38+
API_MEMORY_LIMIT=1g
39+
API_PORT=7910

.gitattributes

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Set default behavior to automatically normalize line endings.
2+
* text=auto
3+
4+
# Set file types that should always have CRLF line endings on checkout.
5+
*.sln text eol=crlf
6+
*.csproj text eol=crlf
7+
*.config text eol=crlf
8+
9+
# Set file types that should always have LF line endings on checkout.
10+
*.sh text eol=lf
11+
*.bash text eol=lf
12+
*.yml text eol=lf
13+
*.yaml text eol=lf
14+
15+
# Ensure that shell scripts are executable
16+
*.sh text eol=lf
17+
deploy.sh text eol=lf
18+
deploy-dev.sh text eol=lf
19+
20+
# PowerShell scripts
21+
*.ps1 text eol=crlf
22+
23+
# Docker files
24+
Dockerfile text eol=lf
25+
*.dockerfile text eol=lf
26+
docker-compose*.yml text eol=lf
27+
28+
# Markdown files
29+
*.md text eol=lf
30+
31+
# JSON files
32+
*.json text eol=lf

.github/workflows/develop.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Develop
2+
3+
on:
4+
pull_request:
5+
branches: [develop]
6+
paths-ignore:
7+
- '**.md'
8+
- 'docs/**'
9+
- 'scripts/**'
10+
11+
env:
12+
DOTNET_VERSION: '8.0.x'
13+
14+
jobs:
15+
build-and-test:
16+
name: Build & Test
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Setup .NET
24+
uses: actions/setup-dotnet@v4
25+
with:
26+
dotnet-version: ${{ env.DOTNET_VERSION }}
27+
28+
- name: Cache NuGet
29+
uses: actions/cache@v4
30+
with:
31+
path: ~/.nuget/packages
32+
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
33+
restore-keys: |
34+
${{ runner.os }}-nuget-
35+
36+
- name: Restore
37+
run: |
38+
echo "복원 중..."
39+
start_time=$(date +%s)
40+
dotnet restore ProjectVG.sln
41+
end_time=$(date +%s)
42+
duration=$((end_time - start_time))
43+
echo "복원 완료 (${duration}초)"
44+
45+
- name: Build
46+
run: |
47+
echo "🔨 빌드 중..."
48+
start_time=$(date +%s)
49+
dotnet build ProjectVG.sln --no-restore --configuration Release
50+
end_time=$(date +%s)
51+
duration=$((end_time - start_time))
52+
echo "빌드 완료 (${duration}초)"
53+
54+
- name: Test
55+
run: |
56+
echo "테스트 중..."
57+
start_time=$(date +%s)
58+
dotnet test --no-build --configuration Release --verbosity normal
59+
end_time=$(date +%s)
60+
duration=$((end_time - start_time))
61+
echo "테스트 완료 (${duration}초)"
62+
63+
- name: Success Status
64+
if: success()
65+
run: |
66+
echo "✅ 빌드 및 테스트 성공"
67+
68+
- name: Build Status
69+
if: failure()
70+
run: |
71+
echo "❌ 빌드 실패"
72+
exit 1

.github/workflows/release.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [release]
6+
paths-ignore:
7+
- '**.md'
8+
- 'docs/**'
9+
- 'scripts/**'
10+
11+
env:
12+
DOTNET_VERSION: '8.0.x'
13+
DOCKER_IMAGE_NAME: ghcr.io/projectvg/projectvgapi
14+
ACTOR: projectvg
15+
16+
permissions:
17+
contents: read
18+
packages: write
19+
20+
jobs:
21+
build:
22+
name: Build & Push
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Setup .NET
30+
uses: actions/setup-dotnet@v4
31+
with:
32+
dotnet-version: ${{ env.DOTNET_VERSION }}
33+
34+
- name: Cache NuGet
35+
uses: actions/cache@v4
36+
with:
37+
path: ~/.nuget/packages
38+
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
39+
restore-keys: |
40+
${{ runner.os }}-nuget-
41+
42+
- name: Restore
43+
run: |
44+
echo "복원 중..."
45+
dotnet restore ProjectVG.sln
46+
echo "복원 완료"
47+
48+
- name: Build
49+
run: |
50+
echo "🔨 빌드 중..."
51+
dotnet build ProjectVG.sln --no-restore --configuration Release
52+
echo "빌드 완료"
53+
54+
- name: Test
55+
run: |
56+
echo "테스트 중..."
57+
dotnet test --no-build --configuration Release --verbosity normal
58+
echo "테스트 완료"
59+
60+
- name: Login GHCR
61+
uses: docker/login-action@v3
62+
with:
63+
registry: ghcr.io
64+
username: ${{ env.ACTOR }}
65+
password: ${{ secrets.GHCR_TOKEN }}
66+
67+
- name: Build & Push Image
68+
run: |
69+
docker build -t ${{ env.DOCKER_IMAGE_NAME }}:latest -f ProjectVG.Api/Dockerfile .
70+
docker push ${{ env.DOCKER_IMAGE_NAME }}:latest
71+
72+
- name: Build Success Status
73+
if: success()
74+
run: |
75+
echo "✅ 빌드 및 이미지 푸시 완료"
76+
echo "이미지: ${{ env.DOCKER_IMAGE_NAME }}:latest"
77+
78+
- name: Build Failure Status
79+
if: failure()
80+
run: |
81+
echo "❌ 빌드 또는 이미지 푸시 실패"
82+
exit 1
83+
84+
deploy:
85+
name: Deploy
86+
needs: build
87+
runs-on: [self-hosted, deploy-runner]
88+
89+
steps:
90+
- name: Checkout
91+
uses: actions/checkout@v4
92+
93+
- name: Login GHCR
94+
run: echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ env.ACTOR }} --password-stdin
95+
96+
- name: Add Config Files
97+
run: |
98+
echo "${{ secrets.PROD_APPLICATION_ENV }}" | base64 --decode > .env
99+
100+
- name: Make Script Executable
101+
run: chmod +x deploy/deploy.sh
102+
103+
- name: Deploy
104+
run: ./deploy/deploy.sh
105+
106+
- name: Cleanup
107+
run: |
108+
docker image prune -f
109+
docker builder prune -f
110+
111+
- name: Deploy Success Status
112+
if: success()
113+
run: |
114+
echo "✅ 배포 완료"
115+
echo "이미지: ${{ env.DOCKER_IMAGE_NAME }}:latest"
116+
117+
- name: Deploy Failure Status
118+
if: failure()
119+
run: |
120+
echo "❌ 배포 실패"
121+
exit 1

.gitignore

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ _ReSharper*/
100100

101101
# Docker
102102
**/Dockerfile.*
103-
docker-compose*
103+
docker-compose.override.yml
104+
105+
# Keep template files but ignore runtime files
106+
!docker-compose.prod.yml
107+
!env.prod.example
104108

105109
# Logs
106110
*.log
@@ -161,5 +165,5 @@ secrets.json
161165
*.xlsx
162166

163167
# Demo files
164-
web_chat_demo.html
165-
time_travel_commit.exe
168+
*.exe
169+
*.ini

ProjectVG.Api/Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ EXPOSE 7900
2929
# 빌드 결과 복사
3030
COPY --from=build /app/publish .
3131

32-
# 헬스체크 추가
33-
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
34-
CMD curl -f http://localhost:7900/health || exit 1
32+
# 헬스체크 추가 - wget 사용
33+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
34+
CMD wget --no-verbose --tries=1 --spider http://localhost:7900/health || exit 1
3535

3636
ENTRYPOINT ["dotnet", "ProjectVG.Api.dll"]

ProjectVG.Tests/Application/Integration/ConversationServiceIntegrationTests.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public async Task GetConversationHistoryAsync_WithExistingMessages_ShouldReturnI
133133
var message3 = await _conversationService.AddMessageAsync(userId, characterId, ChatRole.User, "Third message", DateTime.UtcNow);
134134

135135
// Act
136-
var history = await _conversationService.GetConversationHistoryAsync(userId, characterId, 10);
136+
var history = await _conversationService.GetConversationHistoryAsync(userId, characterId, 1, 10);
137137

138138
// Assert
139139
var historyList = history.ToList();
@@ -160,7 +160,7 @@ public async Task GetConversationHistoryAsync_WithCountLimit_ShouldRespectLimit(
160160
}
161161

162162
// Act - Request only 3 messages
163-
var history = await _conversationService.GetConversationHistoryAsync(userId, characterId, 3);
163+
var history = await _conversationService.GetConversationHistoryAsync(userId, characterId, 1, 3);
164164

165165
// Assert
166166
var historyList = history.ToList();
@@ -215,7 +215,7 @@ public async Task GetConversationHistoryAsync_WithInvalidCount_ShouldThrowValida
215215

216216
// Act & Assert
217217
await Assert.ThrowsAsync<ValidationException>(
218-
() => _conversationService.GetConversationHistoryAsync(userId, characterId, count));
218+
() => _conversationService.GetConversationHistoryAsync(userId, characterId, 1, count));
219219
}
220220

221221
[Fact]
@@ -225,8 +225,8 @@ public async Task GetConversationHistoryAsync_WithMultipleUserCharacterPairs_Sho
225225
await _fixture.ClearDatabaseAsync();
226226
var user1 = await CreateUserAsync("user1", "user1@example.com");
227227
var user2 = await CreateUserAsync("user2", "user2@example.com");
228-
var char1 = await CreateCharacterAsync("Character1");
229-
var char2 = await CreateCharacterAsync("Character2");
228+
var char1 = await CreateCharacterAsync("Character1", user1.Id);
229+
var char2 = await CreateCharacterAsync("Character2", user1.Id);
230230

231231
// Add messages for different user-character combinations
232232
await _conversationService.AddMessageAsync(user1.Id, char1.Id, ChatRole.User, "User1-Char1 Message", DateTime.UtcNow);
@@ -291,8 +291,8 @@ public async Task ClearConversationAsync_ShouldOnlyAffectSpecificUserCharacterPa
291291
await _fixture.ClearDatabaseAsync();
292292
var user1 = await CreateUserAsync("user1", "user1@example.com");
293293
var user2 = await CreateUserAsync("user2", "user2@example.com");
294-
var char1 = await CreateCharacterAsync("Character1");
295-
var char2 = await CreateCharacterAsync("Character2");
294+
var char1 = await CreateCharacterAsync("Character1", user1.Id);
295+
var char2 = await CreateCharacterAsync("Character2", user1.Id);
296296

297297
// Add messages for different combinations
298298
await _conversationService.AddMessageAsync(user1.Id, char1.Id, ChatRole.User, "User1-Char1", DateTime.UtcNow);
@@ -429,8 +429,8 @@ public async Task MultipleConversationsSimultaneously_ShouldIsolateCorrectly()
429429
await _fixture.ClearDatabaseAsync();
430430
var user1 = await CreateUserAsync("user1", "user1@example.com");
431431
var user2 = await CreateUserAsync("user2", "user2@example.com");
432-
var character1 = await CreateCharacterAsync("Character1");
433-
var character2 = await CreateCharacterAsync("Character2");
432+
var character1 = await CreateCharacterAsync("Character1", user1.Id);
433+
var character2 = await CreateCharacterAsync("Character2", user1.Id);
434434

435435
// Create conversations for different user-character pairs
436436
// User1 with Character1
@@ -473,7 +473,7 @@ public async Task MultipleConversationsSimultaneously_ShouldIsolateCorrectly()
473473
private async Task<(Guid userId, Guid characterId)> CreateUserAndCharacterAsync()
474474
{
475475
var user = await CreateUserAsync();
476-
var character = await CreateCharacterAsync();
476+
var character = await CreateCharacterAsync("TestCharacter", user.Id);
477477
return (user.Id, character.Id);
478478
}
479479

@@ -486,9 +486,9 @@ public async Task MultipleConversationsSimultaneously_ShouldIsolateCorrectly()
486486
}
487487

488488
private async Task<ProjectVG.Application.Models.Character.CharacterDto> CreateCharacterAsync(
489-
string name = "TestCharacter")
489+
string name = "TestCharacter", Guid? userId = null)
490490
{
491-
var createCommand = TestDataBuilder.CreateCreateCharacterWithFieldsCommand(name);
491+
var createCommand = TestDataBuilder.CreateCreateCharacterWithFieldsCommand(name, userId: userId);
492492
return await _characterService.CreateCharacterWithFieldsAsync(createCommand);
493493
}
494494

0 commit comments

Comments
 (0)