Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0a811da
fix: dbcontext scope
ImGdevel Aug 16, 2025
197e9ce
feat: AOP 기반 비용 산출
ImGdevel Aug 16, 2025
95b63cc
feat: LLM및 TTS 코스트 계산 로직 작성
ImGdevel Aug 16, 2025
6d9fc88
feat: 사용자 의도 파악 로직의 비용 추적
ImGdevel Aug 16, 2025
b08e756
fix: input/output 수정, 유저 분석 로직 또한 비용추적 시작
ImGdevel Aug 16, 2025
a050c89
feat: 실패 처리 핸들러 등록
ImGdevel Aug 17, 2025
0948bc7
fix: LLM 결과 처리 단계 축소
ImGdevel Aug 17, 2025
d07f011
feat: 모든 DTO를 ChatProcessContext를 통해 처리
ImGdevel Aug 17, 2025
d055636
feat: 요청에서 tts 사용 여부 결정
ImGdevel Aug 18, 2025
058ccfe
feat: JWT 토큰 작성
ImGdevel Aug 23, 2025
665baea
feat: 토큰 관리
ImGdevel Aug 23, 2025
33bb986
feat: api 모듈 작성
ImGdevel Aug 23, 2025
43ed274
feat: redis 설정
ImGdevel Aug 23, 2025
d8c2aa5
feat: JWT 토큰 로그인 로직
ImGdevel Aug 23, 2025
e9a45ff
style: 주석 추가
ImGdevel Aug 23, 2025
2693b96
feat: 테스트 코드 작성
ImGdevel Aug 23, 2025
0420578
crone: 실행 환경 수정
ImGdevel Aug 23, 2025
b56d47f
fix: 테스트 클라이언트 디자인 수정
ImGdevel Aug 23, 2025
ec1627d
feat: OAuth2 스켈레톤 코드
ImGdevel Aug 23, 2025
4630199
feat: oauth2 로그인 구현
ImGdevel Aug 24, 2025
2445094
feat: 토큰 전달 방식을 재정의
ImGdevel Aug 24, 2025
9635689
feat: user 도메인 변경
ImGdevel Aug 25, 2025
e034e76
chore: 생성 파일 제외 규칙 추가
ImGdevel Aug 25, 2025
f1c01e1
feat: UserService 수정
ImGdevel Aug 25, 2025
07ff417
refactoy: oauth2 리팩토링
ImGdevel Aug 25, 2025
dc22658
fix: uid
ImGdevel Aug 25, 2025
cc394c3
feat: state 값이 아닌 exchange로 변경
ImGdevel Aug 25, 2025
56cc9cb
Revert "feat: state 값이 아닌 exchange로 변경"
ImGdevel Aug 25, 2025
2a37cf9
feat: 게스트 로그인 기능 추가
ImGdevel Aug 25, 2025
52e5cf6
fix: UID 길이 변경
ImGdevel Aug 25, 2025
4e91a55
fix: 테스트 클라이언트 변경
ImGdevel Aug 25, 2025
b1edaa7
refactory: auth 로직 리팩토링
ImGdevel Aug 25, 2025
1f79237
refactory: chat 로직의 request form 변경 및 userId JWT로 주입
ImGdevel Aug 25, 2025
ac312b4
feat: 로그인 샐패 로직 통잃화
ImGdevel Aug 25, 2025
3627064
feat: 서비스 제공자 확장에 따른 리패팩토링
ImGdevel Aug 25, 2025
1786b60
fix: 오류 수정
ImGdevel Aug 25, 2025
d69d9f6
fix : 잔 요류 수정
ImGdevel Aug 26, 2025
bf41f0f
feat: 웹소캣 세션 연결 방법 변경
ImGdevel Aug 26, 2025
ec8239f
feat: 메타데이터 필드 제거
ImGdevel Aug 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 82 additions & 30 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,30 +1,82 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
# Git 관련
.git
.gitignore
.gitattributes

# IDE 관련
.vs/
.vscode/
*.user
*.suo
*.userosscache
*.sln.docstates

# Build 결과물
bin/
obj/
[Dd]ebug/
[Rr]elease/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# NuGet 관련
*.nupkg
*.snupkg
**/[Pp]ackages/*
!**/[Pp]ackages/build/
*.nuget.props
*.nuget.targets

# 테스트 관련
**/*[Tt]ests/
**/*[Tt]est/
**/*.Tests/
**/*.Test/
coverage/
*.coverage
*.coveragexml

# 문서
docs/
*.md
README*

# 스크립트
scripts/
*.ps1
*.sh

# Docker 관련
Dockerfile*
docker-compose*
.dockerignore

# 환경 변수
.env
.env.local
.env.development
.env.production

# 임시 파일
*.tmp
*.temp
*.log
*.bak
*.swp
*.swo
*~

# OS 관련
.DS_Store
Thumbs.db

# 프로젝트 특정
test-clients/
mssql_data/
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ publish/
# VS auto-generated
Generated_Code/

# Claude AI related files
CLAUDE.md
claude.md
*CLAUDE.md
*claude.md
.claude/
claude-*
*claude*

# Secrets (if using secrets.json for config)
secrets.json

Expand Down
20 changes: 19 additions & 1 deletion ProjectVG.Api/ApiServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using ProjectVG.Api.Services;
using ProjectVG.Api.Filters;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;

namespace ProjectVG.Api
{
Expand Down Expand Up @@ -45,14 +47,30 @@ public static IServiceCollection AddApiAuthentication(this IServiceCollection se
return services;
}

/// <summary>
/// OAuth2 인증 서비스 (선택적)
/// </summary>
public static IServiceCollection AddOAuth2Authentication(this IServiceCollection services)
{
// OAuth2는 별도 컨트롤러에서 처리하므로 기본 인증만 설정
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie();

return services;
}

/// <summary>
/// 개발용 CORS 정책
/// </summary>
public static IServiceCollection AddDevelopmentCors(this IServiceCollection services)
{
services.AddCors(options => {
options.AddPolicy("AllowAll",
policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
.WithExposedHeaders("X-Access-Token", "X-Refresh-Token", "X-Expires-In", "X-UID"));
});

return services;
Expand Down
87 changes: 37 additions & 50 deletions ProjectVG.Api/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -1,80 +1,67 @@
using Microsoft.AspNetCore.Mvc;
using ProjectVG.Application.Services.User;
using ProjectVG.Application.Models.User;
using ProjectVG.Api.Models.Auth.Request;
using ProjectVG.Api.Models.Auth.Response;
using ProjectVG.Application.Services.Auth;

namespace ProjectVG.Api.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
public class AuthController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<AuthController> _logger;
private readonly IAuthService _authService;

public AuthController(IUserService userService, ILogger<AuthController> logger)
public AuthController(IAuthService authService)
{
_userService = userService;
_logger = logger;
_authService = authService;
}

[HttpPost("register")]
public async Task<ActionResult<AuthResponse>> Register([FromBody] RegisterRequest request)
[HttpPost("refresh")]
public async Task<IActionResult> RefreshToken()
{
var userDto = request.ToUserDto();
var createdUser = await _userService.CreateUserAsync(userDto);
var refreshToken = GetRefreshTokenFromHeader();
var result = await _authService.RefreshTokenAsync(refreshToken);

var response = new AuthResponse
return Ok(new
{
Success = true,
Message = "회원가입이 완료되었습니다.",
UserId = createdUser.Id,
Username = createdUser.Username,
Email = createdUser.Email
};

_logger.LogInformation("새 사용자 회원가입 완료: {Username}", createdUser.Username);
return Ok(response);
success = true,
tokens = result.Tokens,
user = result.User
});
}

[HttpPost("login")]
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
var user = await _userService.GetUserByUsernameAsync(request.Username);
var response = new AuthResponse
var refreshToken = GetRefreshTokenFromHeader();
var success = await _authService.LogoutAsync(refreshToken);

return Ok(new
{
Success = true,
Message = "로그인이 완료되었습니다.",
UserId = user.Id,
Username = user.Username,
Email = user.Email
};

_logger.LogInformation("사용자 로그인 완료: {Username}", user.Username);
return Ok(response);
success = success,
message = success ? "Logout successful" : "Logout failed"
});
}

[HttpGet("check-username/{username}")]
public async Task<ActionResult<CheckResponse>> CheckUsername(string username)
[HttpPost("guest-login")]
public async Task<IActionResult> GuestLogin([FromBody] string guestId)
{
var exists = await _userService.UsernameExistsAsync(username);
return Ok(new CheckResponse
if (string.IsNullOrEmpty(guestId))
{
Exists = exists,
Message = exists ? "이미 사용 중인 사용자명입니다." : "사용 가능한 사용자명입니다."
throw new ValidationException(ErrorCode.GUEST_ID_INVALID);
}

var result = await _authService.LoginWithOAuthAsync("guest", guestId);

return Ok(new
{
success = true,
tokens = result.Tokens,
user = result.User
});
}

[HttpGet("check-email/{email}")]
public async Task<ActionResult<CheckResponse>> CheckEmail(string email)
private string GetRefreshTokenFromHeader()
{
var exists = await _userService.EmailExistsAsync(email);
return Ok(new CheckResponse
{
Exists = exists,
Message = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다."
});
return Request.Headers["X-Refresh-Token"].FirstOrDefault() ?? string.Empty;
}
}
}
40 changes: 21 additions & 19 deletions ProjectVG.Api/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
using ProjectVG.Application.Models.API.Request;
using ProjectVG.Application.Services.Chat;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using ProjectVG.Application.Models.Chat;
using ProjectVG.Application.Models.API.Request;
using ProjectVG.Application.Services.Chat;
using System.Security.Claims;

namespace ProjectVG.Api.Controllers
{
[ApiController]
[Route("api/v1/chat")]
[AllowAnonymous]
public class ChatController : ControllerBase
{
private readonly IChatService _chatService;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ChatController> _logger;

public ChatController(IChatService chatService, IServiceScopeFactory scopeFactory, ILogger<ChatController> logger)
public ChatController(IChatService chatService)
{
_chatService = chatService;
_scopeFactory = scopeFactory;
_logger = logger;
}

[HttpPost]
[JwtAuthentication]
public async Task<IActionResult> ProcessChat([FromBody] ChatRequest request)
{
var command = request.ToProcessChatCommand();
var requestResponse = await _chatService.EnqueueChatRequestAsync(command);
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId) || !Guid.TryParse(userId, out var userGuid))
{
throw new ValidationException(ErrorCode.AUTHENTICATION_FAILED);
}

var command = new ProcessChatCommand
{
UserId = userGuid,
Message = request.Message,
CharacterId = request.CharacterId
};

return Ok(new {
success = true,
status = requestResponse.Status,
message = requestResponse.Message,
sessionId = requestResponse.SessionId,
userId = requestResponse.UserId,
characterId = requestResponse.CharacterId,
requestedAt = requestResponse.RequestedAt
});
var result = await _chatService.EnqueueChatRequestAsync(command);

return Ok(result);
}
}
}
Loading