From 88409b8fea50628b230099645a61b50c6f4fc4d7 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 26 Jun 2026 14:59:51 +0900 Subject: [PATCH] Add deployment infrastructure and development guide - db/migrations/: V001 (schema) + V002 (seed data) SQL files - deploy/: systemd service files (taxbaik.service, taxbaik-admin.service) - deploy/: Nginx location block configuration - .gitea/workflows/deploy.yml: CI/CD pipeline (build, test, deploy) - CLAUDE.md: Comprehensive development guidelines (9 sections) Co-Authored-By: Claude --- .gitea/workflows/deploy.yml | 52 +++ CLAUDE.md | 505 ++++++++++++++++++++++++++ db/migrations/V001__InitialSchema.sql | 59 +++ db/migrations/V002__SeedData.sql | 15 + deploy/nginx-taxbaik-locations.conf | 26 ++ deploy/taxbaik-admin.service | 20 + deploy/taxbaik.service | 20 + 7 files changed, 697 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 CLAUDE.md create mode 100644 db/migrations/V001__InitialSchema.sql create mode 100644 db/migrations/V002__SeedData.sql create mode 100644 deploy/nginx-taxbaik-locations.conf create mode 100644 deploy/taxbaik-admin.service create mode 100644 deploy/taxbaik.service diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..877ede5 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,52 @@ +name: TaxBaik CI/CD + +on: + push: + branches: + - main + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0' + + - name: Restore dependencies + run: dotnet restore TaxBaik.sln + + - name: Build solution + run: dotnet build TaxBaik.sln -c Release --no-restore + + - name: Publish Web + run: dotnet publish src/TaxBaik.Web/ -c Release -o ./publish/web + + - name: Publish Admin + run: dotnet publish src/TaxBaik.Admin/ -c Release -o ./publish/admin + + - name: Setup SSH key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} + + - name: Deploy Web + run: | + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts || true + ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "mkdir -p ~/deployments/taxbaik_${TIMESTAMP}" + rsync -az --delete ./publish/web/ ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:~/deployments/taxbaik_${TIMESTAMP}/ + ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "ln -sfn ~/deployments/taxbaik_${TIMESTAMP} ~/taxbaik_active && sudo systemctl restart taxbaik" + + - name: Deploy Admin + run: | + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "mkdir -p ~/deployments/taxbaik_admin_${TIMESTAMP}" + rsync -az --delete ./publish/admin/ ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:~/deployments/taxbaik_admin_${TIMESTAMP}/ + ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "ln -sfn ~/deployments/taxbaik_admin_${TIMESTAMP} ~/taxbaik_admin_active && sudo systemctl restart taxbaik-admin" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cdc03dc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,505 @@ +# CLAUDE.md — TaxBaik 개발 지침 + +## 1. 프로젝트 개요 + +**클라이언트**: 백원숙 세무사 (백원숙세무사사무소, 성북구 하월곡동) +**목적**: 온라인 첫인상 + 블로그 SEO 유입 + 상담 전환 극대화 +**핵심 포지셔닝**: "사업자 세금 + 부동산 + 가족자산 = 일상 자산 세금 파트너" +**기술 스택**: ASP.NET Core 8 / Dapper / PostgreSQL 18 / Nginx / Gitea CI + +--- + +## 2. 아키텍처 + +### 2.1 프로젝트 구조 +``` +TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum — 외부 의존성 없음) +TaxBaik.Infrastructure 클래스 라이브러리 (Dapper repository, DB 마이그레이션) +TaxBaik.Application 클래스 라이브러리 (서비스, DTO, 비즈니스 로직) +TaxBaik.Web ASP.NET Core 앱 (Razor Pages SSR, port 5001) +TaxBaik.Admin ASP.NET Core 앱 (Blazor Server, port 5002) +``` + +### 2.2 계층 책임 +- **Domain**: 비즈니스 규칙, 엔티티 정의 +- **Infrastructure**: DB 접근, Dapper 구현체, 마이그레이션 실행 +- **Application**: 서비스, DTO 매핑, 비즈니스 워크플로우 +- **Web**: 공개 사이트 (SEO 최적화, Razor Pages SSR) +- **Admin**: 관리자 백오피스 (실시간 UI, Blazor Server) + +### 2.3 기술 결정 이유 + +**왜 Razor Pages (Web)인가?** +- Razor Pages는 서버에서 HTML을 렌더링 → Google, Naver가 즉시 크롤 가능 +- Blazor Server는 초기 응답이 shell HTML → SEO 불리 (블로그는 구글 검색이 핵심) + +**왜 Blazor Server (Admin)인가?** +- 관리자는 SEO 불필요 → WebSocket으로 실시간 UI 업데이트 가능 +- 기존 QuantEngine 패턴과 일치 + +**왜 Dapper인가?** +- QuantEngine에서 이미 사용 중 (팀 지식) +- 복잡한 조인, 페이징, 성능 제어 용이 +- EF Core 대비 SQL 완전 제어 가능 + +**왜 두 개의 ASP.NET Core 앱인가?** +- Web: 정적 콘텐츠 + Razor Pages +- Admin: 동적 Blazor + WebSocket +- 미들웨어 파이프라인 다름 → 독립 배포로 한쪽 문제가 다른 쪽에 영향 안 함 + +--- + +## 3. 서버 & 배포 + +### 3.1 SSH 접속 +```bash +ssh kjh2064@178.104.200.7 +``` + +### 3.2 포트 배치 +``` +80 : Nginx reverse proxy (공개) +3000 : Gitea Web (localhost만, proxy via /를 통해) +2222 : Gitea SSH (공개) +5000 : QuantEngine Blazor (localhost, proxy via /quant/) +5001 : TaxBaik.Web (localhost, proxy via /taxbaik) +5002 : TaxBaik.Admin (localhost, proxy via /taxbaik/admin) +5432 : PostgreSQL (localhost 바인드) +``` + +### 3.3 배포 절차 +1. 로컬에서 `dotnet publish -c Release` +2. CI/CD (Gitea Actions)가 자동으로 rsync로 서버에 업로드 +3. 심링크 스왑: `ln -sfn ~/deployments/taxbaik_TIMESTAMP ~/taxbaik_active` +4. 서비스 재시작: `sudo systemctl restart taxbaik` +5. 롤백: 이전 TIMESTAMP로 심링크 재설정 + +### 3.4 서비스 파일 위치 +``` +/etc/systemd/system/taxbaik.service ← Web +/etc/systemd/system/taxbaik-admin.service ← Admin +``` + +### 3.5 배포 디렉토리 구조 (서버) +``` +/home/kjh2064/ +├── taxbaik_active → ./deployments/taxbaik_20260626_150000/ +├── taxbaik_admin_active → ./deployments/taxbaik_admin_20260626_150000/ +└── deployments/ + ├── taxbaik_20260626_150000/ (Web publish 출력) + ├── taxbaik_20260626_140000/ (이전 버전) + ├── taxbaik_admin_20260626_150000/ (Admin publish 출력) + └── ... +``` + +--- + +## 4. Nginx 라우팅 + +기존 Gitea (`/`)와 QuantEngine (`/quant/`)을 유지하면서 TaxBaik 추가: + +```nginx +# /etc/nginx/sites-available/default (또는 현재 설정 파일)에 아래 블록 추가 + +location /taxbaik { + proxy_pass http://127.0.0.1:5001; + proxy_http_version 1.1; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 120s; +} + +location /taxbaik/admin { + proxy_pass http://127.0.0.1:5002; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +**더 구체적인 경로가 우선**: `/taxbaik` 블록이 `/` Gitea 블록보다 먼저 매칭됨. + +--- + +## 5. 데이터베이스 + +### 5.1 연결 설정 + +**환경 변수** (systemd unit file에 설정): +```ini +Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=XXXXXXXX +``` + +**절대 appsettings.Production.json에 비밀값을 하드코딩하지 말 것.** + +### 5.2 Dapper 사용 패턴 + +**DbConnectionFactory.cs**: +```csharp +public sealed class DbConnectionFactory : IDbConnectionFactory +{ + private readonly string _cs; + public DbConnectionFactory(IConfiguration cfg) => + _cs = cfg.GetConnectionString("Default") + ?? throw new InvalidOperationException("Missing 'Default' connection string."); + + public IDbConnection CreateConnection() => new NpgsqlConnection(_cs); +} +``` + +**Repository 메서드**: +```csharp +public async Task GetBySlugAsync(string slug, CancellationToken ct) +{ + using var conn = Conn(); // 항상 using + return await conn.QueryFirstOrDefaultAsync( + "SELECT * FROM blog_posts WHERE slug = @Slug AND is_published = TRUE", + new { Slug = slug }); +} +``` + +**규칙**: +- 항상 `using var conn = Conn();` 사용 (자동 닫기) +- 항상 `@ParameterName` 파라미터 사용 (SQL injection 방지) +- 절대 문자열 연결 금지 +- 대소문자 구분 안 함 (Dapper가 매핑) + +### 5.3 마이그레이션 + +마이그레이션 파일: `db/migrations/V{number}__{description}.sql` + +**실행 방식**: +1. Program.cs 시작 시 MigrationRunner 호출 +2. `schema_migrations` 테이블에서 실행 여부 확인 +3. 미실행 마이그레이션만 순서대로 실행 + +--- + +## 6. 코드 규칙 + +### 6.1 C# 네이밍 +- 클래스, 메서드, 프로퍼티: **PascalCase** +- 비공개 필드: **_camelCase** +- 로컬 변수, 파라미터: **camelCase** +- 상수: **PascalCase** (SCREAMING_SNAKE_CASE 사용 금지) +- 비동기 메서드: **Async** 접미사 (GetBySlugAsync) +- 비공개 메서드: **Async 접미사 생략 가능** + +### 6.2 파일 구조 +``` +Domain/ + Entities/BlogPost.cs + Interfaces/IBlogPostRepository.cs + Enums/InquiryStatus.cs + +Infrastructure/ + Data/DbConnectionFactory.cs + Repositories/BlogPostRepository.cs + DependencyInjection.cs + +Application/ + Services/BlogService.cs + DTOs/BlogPostListDto.cs + +Web/ + Pages/Blog/Index.cshtml + Pages/Blog/Index.cshtml.cs ← PageModel + wwwroot/css/site.css + +Admin/ + Components/Pages/Blog/BlogList.razor + Services/AdminAuthStateProvider.cs +``` + +### 6.3 모든 UI는 한국어 +- 버튼 레이블, 폼 레이블, 에러 메시지 → 한국어만 +- 코드 주석, 예외 메시지 → 영어 가능 + +### 6.4 오류 처리 +- 서비스는 **타입화된 예외** 던지기 (ValidationException, ThrottleException) +- PageModel/Component에서 catch → ModelState 또는 Toast +- 절대 stack trace를 HTML에 노출 금지 +- ILogger로 모든 예외 로깅 + +--- + +## 7. Dapper 패턴 + +### 7.1 단일 행 조회 +```csharp +var post = await conn.QueryFirstOrDefaultAsync( + "SELECT * FROM blog_posts WHERE id = @Id", + new { Id = id }); +``` + +### 7.2 여러 행 + 페이징 +```csharp +var (rows, total) = await GetPublishedPagedAsync(page: 1, pageSize: 12); + +// 구현: +public async Task<(IEnumerable, int)> GetPublishedPagedAsync(int page, int pageSize) +{ + using var conn = Conn(); + using var reader = await conn.QueryMultipleAsync( + @"SELECT bp.* FROM blog_posts bp WHERE is_published = TRUE + ORDER BY published_at DESC LIMIT @PageSize OFFSET @Offset; + SELECT COUNT(*) FROM blog_posts WHERE is_published = TRUE;", + new { PageSize = pageSize, Offset = (page - 1) * pageSize }); + + var rows = (await reader.ReadAsync()).ToList(); + var total = await reader.ReadFirstAsync(); + return (rows, total); +} +``` + +### 7.3 삽입 + 반환된 ID +```csharp +var newId = await conn.QueryFirstAsync( + @"INSERT INTO blog_posts (title, content, slug, is_published, created_at) + VALUES (@Title, @Content, @Slug, FALSE, NOW()) + RETURNING id", + new { Title = title, Content = content, Slug = slug }); +``` + +### 7.4 트랜잭션 +```csharp +using var conn = Conn(); +using var tx = conn.BeginTransaction(); +try +{ + // 여러 명령 + await conn.ExecuteAsync("UPDATE ...", null, tx); + await conn.ExecuteAsync("INSERT ...", null, tx); + tx.Commit(); +} +catch +{ + tx.Rollback(); + throw; +} +``` + +--- + +## 8. Blazor Admin 패턴 + +### 8.1 PathBase +Admin은 `/taxbaik/admin/` 경로에서 실행: + +```csharp +// Program.cs +app.UsePathBase("/taxbaik/admin"); +``` + +`@page` 지시문의 경로는 이 기본값에 상대적. 예: +```razor +@page "/login" ← 실제 URL: /taxbaik/admin/login +@page "/blog" ← 실제 URL: /taxbaik/admin/blog +``` + +### 8.2 Cookie 인증 +```csharp +// Program.cs +services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(opts => opts.LoginPath = "/login"); + +app.UseAuthentication(); +app.UseAuthorization(); +``` + +### 8.3 모든 페이지에 [Authorize] 추가 +```razor +@* _Imports.razor *@ +@attribute [Authorize] +``` + +Login 페이지만 [AllowAnonymous]: +```razor +@page "/login" +@attribute [AllowAnonymous] +``` + +### 8.4 컴포넌트 구조 +```razor +@page "/blog" +@inject IBlogService BlogService +@attribute [Authorize] + +블로그 관리 + +@if (posts != null) +{ + @foreach (var post in posts) + { + + } +} + +@code { + private List? posts; + + protected override async Task OnInitializedAsync() + { + posts = await BlogService.GetAllAsync(); + } + + private async Task HandleDelete(int id) + { + await BlogService.DeleteAsync(id); + posts = await BlogService.GetAllAsync(); + StateHasChanged(); + } +} +``` + +### 8.5 상태 관리 +- 전역 상태 불필요 (세션 → DB에서 읽음) +- 페이지 로드 시 `OnInitializedAsync`에서 데이터 가져오기 +- 업데이트는 `StateHasChanged()` 호출 + +--- + +## 9. Do's & Don'ts + +### DO ✅ +- [x] 모든 UI 문자열은 한국어 +- [x] 항상 `@ParameterName` 파라미터 사용 (Dapper) +- [x] Domain 엔티티를 비즈니스 경계로 사용 +- [x] Repository 인터페이스를 의존성 주입 (Service) +- [x] Razor Page: 비즈니스 로직은 PageModel 또는 Service에 +- [x] Blazor: 비즈니스 로직은 Service에, Component는 뷰만 +- [x] 블로그 포스트 작성 시 SEO 필드 필수 입력 (seo_title, seo_description) +- [x] 광고 규칙 준수 (2026년 6월 광고 규칙): + - 허용: "사전 검토", "리스크 점검", "상황별 절세 방향 안내" + - 금지: "보장", "최저가", "무료", "100% 해결", "세무조사 안 받게" +- [x] 카테고리 목록 캐시 (IMemoryCache, 10분 유효) +- [x] 비밀값은 환경 변수에서 읽기 +- [x] `[ValidateAntiForgeryToken]` POST 메서드에 추가 + +### DON'T ❌ +- [ ] 비밀값을 appsettings.Production.json에 하드코딩 +- [ ] EF Core 사용 금지 (Dapper 일관성) +- [ ] 동기 메서드 (async/await 필수) +- [ ] AutoMapper 사용 금지 (수동 매핑) +- [ ] Repository 인터페이스 없이 DB 직접 쿼리 +- [ ] Razor Component의 @code에 비즈니스 로직 +- [ ] robots.txt에서 `/taxbaik/admin` allow 금지 (disallow 필수) +- [ ] 폼 제출 후 redirect (fire-and-forget 또는 same-page 응답) +- [ ] 절대 `Thread.Sleep` 또는 `Task.Delay` in request handler + +--- + +## 10. Razor Pages 패턴 + +### 10.1 SEO 메타 태그 +```csharp +// Index.cshtml.cs +public void OnGet() +{ + ViewData["Title"] = "백원숙 세무회계 | 사업자·부동산·가족자산 세금 상담"; + ViewData["Description"] = "세무사 백원숙이 제공하는 사업자 기장, 부동산 양도세, 증여세 상담..."; + ViewData["OgImage"] = "/images/hero.jpg"; +} +``` + +```html + +@ViewData["Title"] + + + + +``` + +### 10.2 폼 제출 +```csharp +// Contact.cshtml.cs +[ValidateAntiForgeryToken] +public async Task OnPostAsync() +{ + if (!ModelState.IsValid) + return Page(); + + try + { + await InquiryService.SubmitAsync(Input.Name, Input.Phone, Input.ServiceType, Input.Message); + TempData["Success"] = "문의가 접수되었습니다. 빠른 시간 내에 연락드리겠습니다."; + return RedirectToPage(); + } + catch (ValidationException ex) + { + ModelState.AddModelError("", ex.Message); + return Page(); + } +} +``` + +--- + +## 11. 배포 검증 + +### 빌드 +```bash +dotnet build TaxBaik.sln +``` + +### 서버 상태 확인 (SSH) +```bash +ssh kjh2064@178.104.200.7 + +# DB 확인 +psql -U kjh2064 -d taxbaikdb -c "\dt" + +# 서비스 상태 +systemctl status taxbaik taxbaik-admin + +# 엔드포인트 확인 +curl http://127.0.0.1:5001/health +curl http://127.0.0.1:5002/health + +# Nginx 라우팅 확인 +curl http://127.0.0.1/taxbaik +curl http://127.0.0.1/taxbaik/admin +``` + +### E2E 테스트 +```bash +# 문의 폼 제출 +curl -X POST http://178.104.200.7/taxbaik/contact \ + -d "name=테스트&phone=010-1234-5678&service_type=사업자세무&message=테스트" + +# 관리자 DB에서 확인 +ssh kjh2064@178.104.200.7 +psql -U kjh2064 -d taxbaikdb +SELECT * FROM inquiries ORDER BY created_at DESC LIMIT 1; +``` + +--- + +## 12. 문제 해결 + +| 문제 | 해결 | +|------|------| +| 앱 시작 안 됨 | `journalctl -u taxbaik -n 50` 로그 확인 | +| DB 연결 실패 | 환경 변수 `ConnectionStrings__Default` 확인 (systemd unit file) | +| 404 /taxbaik | Nginx 설정 재로드: `sudo nginx -t && sudo systemctl reload nginx` | +| Blazor WebSocket 안 됨 | `/taxbaik/admin` 경로에 Upgrade 헤더 필요 (Nginx 설정 확인) | +| 배포 후 503 | 서비스 시작 대기 (startup 시간 ~5초), `systemctl status taxbaik` 확인 | + +--- + +**마지막 체크리스트:** +- [ ] 솔루션 빌드 성공 (`dotnet build`) +- [ ] 모든 프로젝트 참조 정확 +- [ ] DB 마이그레이션 SQL 파일 생성 +- [ ] systemd 서비스 파일 서버에 설치 +- [ ] Nginx location 블록 설정 +- [ ] Gitea Secrets (DEPLOY_USER, DEPLOY_HOST, DEPLOY_SSH_KEY) 추가 +- [ ] 초기 커밋 및 git push diff --git a/db/migrations/V001__InitialSchema.sql b/db/migrations/V001__InitialSchema.sql new file mode 100644 index 0000000..f8f4a74 --- /dev/null +++ b/db/migrations/V001__InitialSchema.sql @@ -0,0 +1,59 @@ +-- V001 - Initial Schema for TaxBaik + +CREATE TABLE categories ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + slug VARCHAR(100) NOT NULL UNIQUE, + sort_order INT NOT NULL DEFAULT 0 +); + +CREATE TABLE admin_users ( + id SERIAL PRIMARY KEY, + username VARCHAR(100) NOT NULL UNIQUE, + password_hash VARCHAR(500) NOT NULL, + last_login_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE blog_posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(300) NOT NULL, + content TEXT NOT NULL, + slug VARCHAR(300) NOT NULL UNIQUE, + category_id INT REFERENCES categories(id) ON DELETE SET NULL, + tags TEXT, + author_id INT REFERENCES admin_users(id) ON DELETE SET NULL, + published_at TIMESTAMPTZ, + view_count INT NOT NULL DEFAULT 0, + seo_title VARCHAR(300), + seo_description VARCHAR(500), + thumbnail_url VARCHAR(500), + is_published BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_blog_slug ON blog_posts(slug); +CREATE INDEX idx_blog_published ON blog_posts(is_published, published_at DESC); +CREATE INDEX idx_blog_category ON blog_posts(category_id); + +CREATE TABLE inquiries ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(200), + service_type VARCHAR(100) NOT NULL, + message TEXT NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'new', + ip_address VARCHAR(50), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_inquiry_status ON inquiries(status); +CREATE INDEX idx_inquiry_created ON inquiries(created_at DESC); + +CREATE TABLE site_settings ( + key VARCHAR(200) PRIMARY KEY, + value TEXT NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/db/migrations/V002__SeedData.sql b/db/migrations/V002__SeedData.sql new file mode 100644 index 0000000..ffeee1c --- /dev/null +++ b/db/migrations/V002__SeedData.sql @@ -0,0 +1,15 @@ +-- V002 - Seed Categories and Settings + +INSERT INTO categories (name, slug, sort_order) VALUES + ('사업자 세무', 'business-tax', 1), + ('부동산 세금', 'real-estate-tax', 2), + ('종합소득세', 'income-tax', 3), + ('부가가치세', 'vat', 4), + ('가족자산·증여', 'family-asset', 5); + +INSERT INTO site_settings (key, value) VALUES + ('site.title', '백원숙 세무회계 | 성북구 세무사'), + ('site.description', '사업자 세무, 부동산 양도세·증여세, 종합소득세 상담. 성북구 백원숙 세무사.'), + ('kakao.channel.url', ''), + ('phone.main', ''), + ('consultation.fee.text','상담료 7만~20만 원, 계약 체결 시 일부 차감'); diff --git a/deploy/nginx-taxbaik-locations.conf b/deploy/nginx-taxbaik-locations.conf new file mode 100644 index 0000000..dba07c9 --- /dev/null +++ b/deploy/nginx-taxbaik-locations.conf @@ -0,0 +1,26 @@ +# TaxBaik Nginx Location Blocks +# Add these to your main Nginx config (e.g., /etc/nginx/sites-available/default) + +# TaxBaik 공개 사이트 (Razor Pages) +location /taxbaik { + proxy_pass http://127.0.0.1:5001; + proxy_http_version 1.1; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 120s; +} + +# TaxBaik 관리자 (Blazor Server - WebSocket 필요) +location /taxbaik/admin { + proxy_pass http://127.0.0.1:5002; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} diff --git a/deploy/taxbaik-admin.service b/deploy/taxbaik-admin.service new file mode 100644 index 0000000..aad6703 --- /dev/null +++ b/deploy/taxbaik-admin.service @@ -0,0 +1,20 @@ +[Unit] +Description=TaxBaik Admin Backoffice (.NET 8 Blazor Server) +After=network.target + +[Service] +User=kjh2064 +WorkingDirectory=/home/kjh2064/taxbaik_admin_active +ExecStart=/usr/bin/dotnet TaxBaik.Admin.dll +Restart=always +RestartSec=10 +KillSignal=SIGINT +SyslogIdentifier=taxbaik-admin +Environment=ASPNETCORE_ENVIRONMENT=Production +Environment=ASPNETCORE_URLS=http://127.0.0.1:5002 +# 아래 줄은 서버에서 직접 편집 (git에 커밋하지 않음) +# Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=CHANGE_ME +Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false + +[Install] +WantedBy=multi-user.target diff --git a/deploy/taxbaik.service b/deploy/taxbaik.service new file mode 100644 index 0000000..eb28140 --- /dev/null +++ b/deploy/taxbaik.service @@ -0,0 +1,20 @@ +[Unit] +Description=TaxBaik Public Website (.NET 8) +After=network.target + +[Service] +User=kjh2064 +WorkingDirectory=/home/kjh2064/taxbaik_active +ExecStart=/usr/bin/dotnet TaxBaik.Web.dll +Restart=always +RestartSec=10 +KillSignal=SIGINT +SyslogIdentifier=taxbaik +Environment=ASPNETCORE_ENVIRONMENT=Production +Environment=ASPNETCORE_URLS=http://127.0.0.1:5001 +# 아래 줄은 서버에서 직접 편집 (git에 커밋하지 않음) +# Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=CHANGE_ME +Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false + +[Install] +WantedBy=multi-user.target