From 233e28eefbb68c5e7e15e81d78adc06e9b71fd98 Mon Sep 17 00:00:00 2001 From: Yeonu-Kim Date: Tue, 23 Jun 2026 15:04:08 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20local=20import=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=EB=8B=A8=20import=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/seed.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/db/seed.py b/app/db/seed.py index 39ad24c..e4b2be7 100644 --- a/app/db/seed.py +++ b/app/db/seed.py @@ -10,6 +10,7 @@ ReviewSubmission, ) from app.auth.models import EmailVerification, User # noqa: F401 +from app.blockchain.service_impl import BlockchainServiceImpl from app.core.config import settings from app.core.security import get_password_hash from app.db.base import Base @@ -24,8 +25,6 @@ async def _deploy_or_none() -> str | None: if not settings.blockchain_rpc_url or not settings.server_private_key: return None try: - from app.blockchain.service_impl import BlockchainServiceImpl - return await BlockchainServiceImpl().deploy_contract() except Exception: logger.exception("[SEED] contract deploy failed, skipping") @@ -34,8 +33,6 @@ async def _deploy_or_none() -> str | None: async def _fund_contract(contract_address: str, amount_wei: int) -> None: try: - from app.blockchain.service_impl import BlockchainServiceImpl - await BlockchainServiceImpl().fund_contract(contract_address, amount_wei) except Exception: logger.exception("[SEED] contract fund failed contract=%s", contract_address) From 5005164f86a0e22a2dc84787a2d2452d2cac1d59 Mon Sep 17 00:00:00 2001 From: Yeonu-Kim Date: Tue, 23 Jun 2026 15:11:39 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=A5=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20models=20=ED=8F=B4=EB=8D=94=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/__init__.py | 0 app/models/email_verification.py | 1 - app/models/event.py | 1 - app/models/user.py | 1 - tests/test_auth.py | 3 +-- 5 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 app/models/__init__.py delete mode 100644 app/models/email_verification.py delete mode 100644 app/models/event.py delete mode 100644 app/models/user.py diff --git a/app/models/__init__.py b/app/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/email_verification.py b/app/models/email_verification.py deleted file mode 100644 index 2aae3b4..0000000 --- a/app/models/email_verification.py +++ /dev/null @@ -1 +0,0 @@ -from app.auth.models import EmailVerification as EmailVerification # noqa: F401 diff --git a/app/models/event.py b/app/models/event.py deleted file mode 100644 index 0a5c23b..0000000 --- a/app/models/event.py +++ /dev/null @@ -1 +0,0 @@ -from app.event.models import Event as Event # noqa: F401 diff --git a/app/models/user.py b/app/models/user.py deleted file mode 100644 index 88f124d..0000000 --- a/app/models/user.py +++ /dev/null @@ -1 +0,0 @@ -from app.auth.models import User as User # noqa: F401 diff --git a/tests/test_auth.py b/tests/test_auth.py index 8f72d15..705c35c 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -4,8 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import create_refresh_token, verify_password -from app.models.email_verification import EmailVerification -from app.models.user import User +from app.auth.models import EmailVerification, User from tests.conftest import auth_headers, create_user, create_verification From bebb9fbc094d224fbbbda1b03ae63ffaa3bca1a7 Mon Sep 17 00:00:00 2001 From: Yeonu-Kim Date: Tue, 23 Jun 2026 15:26:48 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20seed=EB=A5=BC=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20class=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/seed.py | 260 ++++++++++++++++++++++++++----------------------- app/main.py | 12 ++- 2 files changed, 145 insertions(+), 127 deletions(-) diff --git a/app/db/seed.py b/app/db/seed.py index e4b2be7..46d1dd3 100644 --- a/app/db/seed.py +++ b/app/db/seed.py @@ -2,143 +2,155 @@ from typing import cast from uuid import uuid4 -from sqlalchemy.ext.asyncio import AsyncEngine +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker -from app.application.models import ( # noqa: F401 - Application, - ReviewImage, - ReviewSubmission, -) -from app.auth.models import EmailVerification, User # noqa: F401 -from app.blockchain.service_impl import BlockchainServiceImpl -from app.core.config import settings +from app.auth.models import User +from app.blockchain.service import BlockchainService from app.core.security import get_password_hash from app.db.base import Base -from app.db.session import AsyncSessionLocal from app.event.models import Event from app.store.models import Store logger = logging.getLogger(__name__) -async def _deploy_or_none() -> str | None: - if not settings.blockchain_rpc_url or not settings.server_private_key: - return None - try: - return await BlockchainServiceImpl().deploy_contract() - except Exception: - logger.exception("[SEED] contract deploy failed, skipping") - return None +class Seeder: + def __init__( + self, + engine: AsyncEngine, + session_factory: async_sessionmaker[AsyncSession], + blockchain_service: BlockchainService | None, + ) -> None: + self._engine = engine + self._sessions = session_factory + self._blockchain = blockchain_service + async def run(self) -> None: + await self._reset() + await self._seed() -async def _fund_contract(contract_address: str, amount_wei: int) -> None: - try: - await BlockchainServiceImpl().fund_contract(contract_address, amount_wei) - except Exception: - logger.exception("[SEED] contract fund failed contract=%s", contract_address) + async def _reset(self) -> None: + async with self._engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + async def _deploy_contract(self) -> str | None: + if not self._blockchain: + return None + try: + return await self._blockchain.deploy_contract() + except Exception: + logger.exception("[SEED] contract deploy failed, skipping") + return None -async def reset_and_seed(engine: AsyncEngine) -> None: - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - await conn.run_sync(Base.metadata.create_all) + async def _fund_contract(self, contract_address: str, amount_wei: int) -> None: + if not self._blockchain: + return + try: + await self._blockchain.fund_contract(contract_address, amount_wei) + except Exception: + logger.exception( + "[SEED] contract fund failed contract=%s", contract_address + ) - async with AsyncSessionLocal() as session: - # --- 사장님 계정 --- - owner1 = User( - id=uuid4(), - username="김철수", - email="owner1@test.com", - password_hash=get_password_hash("password123"), - role="OWNER", - ) - owner2 = User( - id=uuid4(), - username="이영희", - email="owner2@test.com", - password_hash=get_password_hash("password123"), - role="OWNER", - ) - session.add_all([owner1, owner2]) - await session.flush() + async def _seed(self) -> None: + async with self._sessions() as session: + owner1 = User( + id=uuid4(), + username="김철수", + email="owner1@test.com", + password_hash=get_password_hash("password123"), + role="OWNER", + ) + owner2 = User( + id=uuid4(), + username="이영희", + email="owner2@test.com", + password_hash=get_password_hash("password123"), + role="OWNER", + ) + session.add_all([owner1, owner2]) + await session.flush() - # --- 가게 --- - store1 = Store( - id=uuid4(), - name="맛있는 삼겹살", - address="서울시 강남구 테헤란로 1길 10", - category="RESTAURANT", - description="신선한 국내산 돼지고기 전문점", - owner_id=owner1.id, - ) - store2 = Store( - id=uuid4(), - name="카페 브루잉", - address="서울시 마포구 홍익로 5길 3", - category="CAFE", - description="스페셜티 원두를 직접 로스팅하는 카페", - owner_id=owner1.id, - ) - store3 = Store( - id=uuid4(), - name="에이블 패션", - address="서울시 중구 명동길 20", - category="FASHION", - description="트렌디한 여성 의류 편집샵", - owner_id=owner2.id, - ) - store4 = Store( - id=uuid4(), - name="글로우 뷰티살롱", - address="서울시 서초구 반포대로 8길 15", - category="BEAUTY", - description="피부 관리 및 네일 전문 뷰티샵", - owner_id=owner2.id, - ) - session.add_all([store1, store2, store3, store4]) - await session.flush() + store1 = Store( + id=uuid4(), + name="맛있는 삼겹살", + address="서울시 강남구 테헤란로 1길 10", + category="RESTAURANT", + description="신선한 국내산 돼지고기 전문점", + owner_id=owner1.id, + ) + store2 = Store( + id=uuid4(), + name="카페 브루잉", + address="서울시 마포구 홍익로 5길 3", + category="CAFE", + description="스페셜티 원두를 직접 로스팅하는 카페", + owner_id=owner1.id, + ) + store3 = Store( + id=uuid4(), + name="에이블 패션", + address="서울시 중구 명동길 20", + category="FASHION", + description="트렌디한 여성 의류 편집샵", + owner_id=owner2.id, + ) + store4 = Store( + id=uuid4(), + name="글로우 뷰티살롱", + address="서울시 서초구 반포대로 8길 15", + category="BEAUTY", + description="피부 관리 및 네일 전문 뷰티샵", + owner_id=owner2.id, + ) + session.add_all([store1, store2, store3, store4]) + await session.flush() - # --- 이벤트 (각각 컨트랙트 배포) --- - event_specs = [ - dict( - title="삼겹살 맛집 블로그 리뷰 모집", - condition="네이버 블로그 사진 5장 이상, 300자 이상 리뷰 작성", - reward=10_000_000_000_000_000, # 0.01 ETH - is_active=True, - store_id=store1.id, - ), - dict( - title="삼겹살집 인스타그램 리뷰", - condition="인스타그램에 해시태그 #맛있는삼겹살 포함 게시물 업로드", - reward=5_000_000_000_000_000, # 0.005 ETH - is_active=True, - store_id=store1.id, - ), - dict( - title="카페 브루잉 음료 리뷰", - condition="네이버 지도 리뷰 작성", - reward=3_000_000_000_000_000, # 0.003 ETH - is_active=True, - store_id=store2.id, - ), - dict( - title="에이블 패션 스타일링 후기", - condition="구매 후 착용샷과 함께 SNS 게시", - reward=15_000_000_000_000_000, # 0.015 ETH - is_active=True, - store_id=store3.id, - ), - dict( - title="글로우 뷰티살롱 시술 후기", - condition="카카오맵 리뷰 200자 이상 + 사진 3장 이상 첨부", - reward=8_000_000_000_000_000, # 0.008 ETH - is_active=False, - store_id=store4.id, - ), - ] - for spec in event_specs: - contract_address = await _deploy_or_none() - if contract_address: - await _fund_contract(contract_address, cast(int, spec["reward"])) - session.add(Event(id=uuid4(), contract_address=contract_address, **spec)) - await session.commit() + event_specs = [ + dict( + title="삼겹살 맛집 블로그 리뷰 모집", + condition="네이버 블로그 사진 5장 이상, 300자 이상 리뷰 작성", + reward=10_000_000_000_000_000, # 0.01 ETH + is_active=True, + store_id=store1.id, + ), + dict( + title="삼겹살집 인스타그램 리뷰", + condition="인스타그램에 해시태그 #맛있는삼겹살 포함 게시물 업로드", + reward=5_000_000_000_000_000, # 0.005 ETH + is_active=True, + store_id=store1.id, + ), + dict( + title="카페 브루잉 음료 리뷰", + condition="네이버 지도 리뷰 작성", + reward=3_000_000_000_000_000, # 0.003 ETH + is_active=True, + store_id=store2.id, + ), + dict( + title="에이블 패션 스타일링 후기", + condition="구매 후 착용샷과 함께 SNS 게시", + reward=15_000_000_000_000_000, # 0.015 ETH + is_active=True, + store_id=store3.id, + ), + dict( + title="글로우 뷰티살롱 시술 후기", + condition="카카오맵 리뷰 200자 이상 + 사진 3장 이상 첨부", + reward=8_000_000_000_000_000, # 0.008 ETH + is_active=False, + store_id=store4.id, + ), + ] + for spec in event_specs: + contract_address = await self._deploy_contract() + if contract_address: + await self._fund_contract( + contract_address, cast(int, spec["reward"]) + ) + session.add( + Event(id=uuid4(), contract_address=contract_address, **spec) + ) + await session.commit() diff --git a/app/main.py b/app/main.py index c3e958e..06fc546 100644 --- a/app/main.py +++ b/app/main.py @@ -8,10 +8,11 @@ from app.application.router import router as application_router from app.auth.router import router as auth_router +from app.blockchain.service_impl import BlockchainServiceImpl from app.core.config import settings from app.core.exceptions import AppException -from app.db.seed import reset_and_seed -from app.db.session import engine +from app.db.seed import Seeder +from app.db.session import AsyncSessionLocal, engine from app.event.router import router as event_router from app.s3.router import router as s3_router from app.store.router import router as store_router @@ -20,7 +21,12 @@ @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: if settings.seed_db: - await reset_and_seed(engine) + blockchain = ( + BlockchainServiceImpl() + if settings.blockchain_rpc_url and settings.server_private_key + else None + ) + await Seeder(engine, AsyncSessionLocal, blockchain).run() yield From 8f2548cc413fb9007e33a99ef9e2ec2699aa9bbe Mon Sep 17 00:00:00 2001 From: Yeonu-Kim Date: Tue, 23 Jun 2026 15:31:06 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20=ED=8F=AC=EB=A7=A4=ED=8C=85?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index 705c35c..40fbc1a 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -3,8 +3,8 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.core.security import create_refresh_token, verify_password from app.auth.models import EmailVerification, User +from app.core.security import create_refresh_token, verify_password from tests.conftest import auth_headers, create_user, create_verification