diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..c3f502a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 디폴트 무시된 파일
+/shelf/
+/workspace.xml
+# 에디터 기반 HTTP 클라이언트 요청
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4444b22
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..8b80aa3
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/testing-python-week2.iml b/.idea/testing-python-week2.iml
new file mode 100644
index 0000000..eccea1f
--- /dev/null
+++ b/.idea/testing-python-week2.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/api/users.py b/app/api/users.py
index 043dd98..443fc1c 100644
--- a/app/api/users.py
+++ b/app/api/users.py
@@ -1,5 +1,6 @@
-from fastapi import APIRouter, File, Form, UploadFile
+from fastapi import APIRouter, File, Form, UploadFile, HTTPException
from app.models.user import UserResponse
+from app.services.user_service import *
router = APIRouter()
@@ -7,22 +8,44 @@
@router.post("/users/register", response_model=UserResponse)
async def register(user_id: str = Form(...), image: UploadFile = File(...)):
"""회원 등록 엔드포인트"""
- pass
+ image_data = await image.read()
+ registered_user = register_user(user_id, image_data)
+
+ return UserResponse(
+ user_id=registered_user['user_id'],
+ registered_at=registered_user['registered_at']
+ )
@router.post("/users/authenticate")
async def authenticate(image: UploadFile = File(...)):
"""회원 인증 엔드포인트"""
- pass
+ image_data = await image.read()
+ user_id = authenticate_user(image_data)
+
+ if user_id is None:
+ raise HTTPException(status_code=401, detail='인증 실패: 사용자 없음')
+
+ return {'user_id': user_id}
@router.get("/users/{user_id}")
def get_user_info(user_id: str):
"""회원 정보 조회 엔드포인트"""
- pass
+ user_info = get_user(user_id)
+
+ if user_info is None:
+ raise HTTPException(status_code=404, detail='사용자가 존재하지 않습니다.')
+
+ return user_info
@router.delete("/users/{user_id}")
def delete_user_info(user_id: str):
"""회원 삭제 엔드포인트"""
- pass
+ result = delete_user(user_id)
+
+ if not result:
+ raise HTTPException(status_code=404, detail='사용자가 존재하지 않습니다.')
+
+ return {'message': '사용자가 성공적으로 삭제되었습니다.'}
\ No newline at end of file
diff --git a/app/face/face_db.py b/app/face/face_db.py
index 380ae3a..7378b99 100644
--- a/app/face/face_db.py
+++ b/app/face/face_db.py
@@ -1,16 +1,22 @@
-DB_PATH = "face_db.json"
+import json, os
+from datetime import datetime
+DB_PATH = "face_db.json"
def save_user(user_id, embedding):
"""사용자 등록"""
- pass
+ db = load_db()
+ db[user_id] = {'embedding': embedding, 'registered_at': str(datetime.now())}
+ with open(DB_PATH, 'w') as f:
+ json.dump(db, f)
def load_db():
"""데이터베이스 로드"""
- pass
+ if not os.path.exists(DB_PATH): return {}
+ with open(DB_PATH, 'r') as f: return json.load(f)
def save_db(db):
"""데이터베이스 저장"""
- pass
+ with open(DB_PATH, 'w') as f: json.dump(db, f)
\ No newline at end of file
diff --git a/app/face/face_embedding.py b/app/face/face_embedding.py
index 3acfb0c..2de0104 100644
--- a/app/face/face_embedding.py
+++ b/app/face/face_embedding.py
@@ -1,8 +1,12 @@
-def extract_embedding(image):
- """이미지에서 임베딩 추출"""
- pass
+import numpy as np
+from typing import Union
+from deepface import DeepFace
+def extract_embedding(img_path: Union[str, np.ndarray]) -> list:
+ embedding = DeepFace.represent(img_path)[0]['embedding']
+ return embedding
-def verify_embedding(embedding1, embedding2):
+def verify_embedding(embedding1: list, embedding2: list) -> bool:
"""두 임베딩이 같은 사람인지 검증"""
- pass
+ is_same_person = DeepFace.verify(embedding1, embedding2)['verified']
+ return is_same_person
\ No newline at end of file
diff --git a/app/services/user_service.py b/app/services/user_service.py
index 410b807..15d91d9 100644
--- a/app/services/user_service.py
+++ b/app/services/user_service.py
@@ -1,18 +1,50 @@
+import cv2
+import numpy as np
+from datetime import datetime
+
+from app.face.face_embedding import *
+from app.face.face_db import *
+
def register_user(user_id: str, image_bytes: bytes) -> dict:
"""이미지와 ID로 회원 등록"""
- pass
+ image_np = np.frombuffer(image_bytes, dtype=np.uint8)
+ image = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
+ embedding = extract_embedding(image)
+ save_user(user_id, embedding)
+ return {'user_id': user_id, 'registered_at': str(datetime.now())}
def authenticate_user(image_bytes: bytes):
"""이미지로 회원 인증"""
- pass
+ image_np = np.frombuffer(image_bytes, dtype=np.uint8)
+ image = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
+ db = load_db()
+
+ embedding_to_check = extract_embedding(image)
+
+ for user_id, data in db.items():
+ if verify_embedding(data['embedding'], embedding_to_check):
+ return user_id
+
+ return None # 인증 실패
def get_user(user_id):
"""user_id로 회원 정보 조회"""
- pass
+ db = load_db()
+ user_data = db.get(user_id)
+
+ if user_data:
+ return {'user_id': user_id, 'registered_at': user_data['registered_at']}
+ else:
+ return None # 사용자 없음
def delete_user(user_id):
"""user_id로 회원 삭제"""
- pass
+ db = load_db()
+ if user_id in db:
+ del db[user_id]
+ save_db(db)
+ return True
+ return False
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
index e69de29..cfead72 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -0,0 +1,20 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from app.face.face_db import save_user
+from app.face.face_embedding import extract_embedding
+from app.main import app
+
+@pytest.fixture(scope='module')
+def client():
+ with TestClient(app) as c:
+ yield c
+
+@pytest.fixture(scope='module')
+def setup_user_db():
+ image_paths = ['images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg']
+ user_ids = ['aaron_peirsol']
+ for image_path, user_id in zip(image_paths, user_ids):
+ embedding = extract_embedding(image_path)
+ save_user(user_id, embedding)
+ yield
\ No newline at end of file
diff --git a/tests/test_api_users.py b/tests/test_api_users.py
index e69de29..70d00a3 100644
--- a/tests/test_api_users.py
+++ b/tests/test_api_users.py
@@ -0,0 +1,59 @@
+def test_register_user_api(client):
+ # Given
+ image_path = 'images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg'
+ user_id = 'aaron_peirsol'
+
+ # When
+ with open(image_path, 'rb') as img_file:
+ response = client.post(
+ '/users/register',
+ data={'user_id': user_id},
+ files={'image': ('user1.jpg', img_file, 'image/jpeg')},
+ )
+
+ # Then
+ assert response.status_code == 200
+ data = response.json()
+ assert data['user_id'] == user_id
+ assert data['registered_at'] is not None
+
+def test_authenticate_user_api(client, setup_user_db):
+ # Given
+ image_path = 'images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg'
+
+ # When
+ with open(image_path, 'rb') as img_file:
+ response = client.post(
+ '/users/authenticate',
+ files={'image': ('user1.jpg', img_file, 'image/jpeg')},
+ )
+
+ assert response.status_code == 200
+ data = response.json()
+ assert data['user_id'] == 'aaron_peirsol'
+
+def test_get_registered_user_api(client, setup_user_db):
+ # Given
+ user_id = 'aaron_peirsol'
+
+ # When
+ response = client.get(f'/users/{user_id}')
+
+ # Then
+ assert response.status_code == 200
+ data = response.json()
+ assert data['user_id'] == user_id
+ assert 'registered_at' in data
+
+def test_delete_registered_user_api(client, setup_user_db):
+ # Given
+ user_id = 'aaron_peirsol'
+
+ # When
+ response = client.delete(f'/users/{user_id}')
+
+ # Then
+ assert response.status_code == 200
+
+ response = client.get(f'/users/{user_id}')
+ assert response.status_code == 404
\ No newline at end of file
diff --git a/tests/test_face.py b/tests/test_face.py
index e69de29..8de4e03 100644
--- a/tests/test_face.py
+++ b/tests/test_face.py
@@ -0,0 +1,29 @@
+import pytest
+
+from app.face.face_embedding import extract_embedding, verify_embedding
+
+def test_extract_embedding():
+ image_path = "images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg"
+
+ embedding = extract_embedding(image_path)
+
+ assert embedding is not None
+ assert len(embedding) > 0
+
+def test_no_face_exception():
+ image_path = "tests/images/no_face.jpg"
+
+ with pytest.raises(ValueError):
+ extract_embedding(image_path)
+
+def test_verify_embedding():
+ image_path1 = "images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg"
+ image_path2 = "images/Aaron_Peirsol/Aaron_Peirsol_0002.jpg"
+ image_path3 = "images/Olivia_Newton-John/Olivia_Newton-John_0001.jpg"
+
+ embedding1 = extract_embedding(image_path1)
+ embedding2 = extract_embedding(image_path2)
+ embedding3 = extract_embedding(image_path3)
+
+ assert verify_embedding(embedding1, embedding2)
+ assert not verify_embedding(embedding1, embedding3)
\ No newline at end of file
diff --git a/tests/test_service_users.py b/tests/test_service_users.py
index e69de29..d861f62 100644
--- a/tests/test_service_users.py
+++ b/tests/test_service_users.py
@@ -0,0 +1,73 @@
+from app.services.user_service import *
+
+def test_register_user():
+ # Given
+ image_path = 'images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg'
+ user_id = 'aaron_peirsol'
+
+ # When:
+ with open(image_path, 'rb') as f:
+ image_data = f.read()
+ result = register_user(user_id, image_data)
+
+ # Then
+ assert result['user_id'] == 'aaron_peirsol'
+ assert result['registered_at'] is not None
+
+
+
+def test_authenticate_registered_user(setup_user_db):
+ # Given
+ image_path = "images/Aaron_Peirsol/Aaron_Peirsol_0001.jpg"
+ # When
+ with open(image_path, 'rb') as f:
+ image_data = f.read()
+ user_id = authenticate_user(image_data)
+ # Then
+ assert user_id == 'aaron_peirsol'
+
+def test_authenticate_unregistered_user(setup_user_db):
+ # Given
+ image_path = 'images/Natasha_McElhone/Natasha_McElhone_0001.jpg'
+ with open(image_path, 'rb') as f:
+ image_data = f.read()
+
+ # When
+ user_id = authenticate_user(image_data)
+
+ # Then
+ assert user_id is None
+
+def test_get_registered_user(setup_user_db):
+ # Given
+ user_id = 'aaron_peirsol'
+
+ # When
+ user_info = get_user(user_id)
+
+ # Then
+ assert user_info['user_id'] == user_id
+ assert 'registered_at' in user_info
+
+def test_delete_registered_user(setup_user_db):
+ # Given
+ user_id = 'aaron_peirsol'
+
+ # When
+ result = delete_user(user_id)
+
+ # Then
+ assert result is True
+
+ user_id = get_user(user_id)
+ assert user_id is None
+
+def test_non_authenticate_user(setup_user_db):
+ # Given: 등록되지 않은 회원
+ user_id = 'non_existent_user'
+
+ # When
+ user = get_user(user_id) # 등록되지 않은 사용자를
+
+ # Then
+ assert user is None # 사용자가 존재하지 않음
\ No newline at end of file