Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
260 changes: 136 additions & 124 deletions app/db/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
12 changes: 9 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down
Empty file removed app/models/__init__.py
Empty file.
1 change: 0 additions & 1 deletion app/models/email_verification.py

This file was deleted.

1 change: 0 additions & 1 deletion app/models/event.py

This file was deleted.

1 change: 0 additions & 1 deletion app/models/user.py

This file was deleted.

3 changes: 1 addition & 2 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth.models import EmailVerification, User
from app.core.security import create_refresh_token, verify_password
from app.models.email_verification import EmailVerification
from app.models.user import User
from tests.conftest import auth_headers, create_user, create_verification


Expand Down
Loading