diff --git a/GithubAquarium/urls.py b/GithubAquarium/urls.py
index c07dbb7..36a5bab 100644
--- a/GithubAquarium/urls.py
+++ b/GithubAquarium/urls.py
@@ -58,4 +58,8 @@
# Include URL configurations from the 'repositories' and 'users' apps
path('api/repositories/', include('apps.repositories.urls')),
path('api/users/', include('apps.users.urls')),
+ path('api/aquatics/', include('apps.aquatics.urls')),
+
+ # --- DRF Auth ---
+ path('api-auth/', include('rest_framework.urls')),
]
\ No newline at end of file
diff --git a/GithubAquarium/views.py b/GithubAquarium/views.py
index 2e27592..0293bd3 100644
--- a/GithubAquarium/views.py
+++ b/GithubAquarium/views.py
@@ -1,12 +1,8 @@
-# GithubAquarium/views.py
-"""
-Core views for the GithubAquarium project.
-"""
-
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
from django.conf import settings
+from rest_framework.permissions import AllowAny
class GitHubLogin(SocialLoginView):
"""
@@ -18,4 +14,7 @@ class GitHubLogin(SocialLoginView):
"""
adapter_class = GitHubOAuth2Adapter
callback_url = settings.GITHUB_CALLBACK_URL
- client_class = OAuth2Client
\ No newline at end of file
+ client_class = OAuth2Client
+ # [추가] 이 뷰는 로그인 전이므로 누구나 접근 가능해야 함
+ permission_classes = (AllowAny,)
+ authentication_classes = []
diff --git a/apps/aquatics/renderer/__init__.py b/apps/aquatics/renderer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/aquatics/renderer/sprite.py b/apps/aquatics/renderer/sprite.py
new file mode 100644
index 0000000..570e91c
--- /dev/null
+++ b/apps/aquatics/renderer/sprite.py
@@ -0,0 +1,103 @@
+import random
+from .utils import strip_outer_svg
+
+def render_fish_group(cf, width, height,mode):
+ species = cf.fish_species
+ raw_svg = species.svg_template
+ inner = strip_outer_svg(raw_svg)
+ fish_id = cf.id
+
+ # === 레이블 내용 ===
+ if mode == "aquarium":
+ label_text = cf.contributor.repository.name
+ else: # fishtank
+ label_text = cf.contributor.user.username
+
+ # === 랜덤 요소 생성 ===
+ start_x = random.uniform(width * 0.1, width * 0.9)
+ start_y = random.uniform(height * 0.2, height * 0.8)
+ amplitude_x = random.uniform(width * 0.25, width * 0.45) # x 왕복폭
+ amplitude_y = random.uniform(height * 0.05, height * 0.12) # y 파동
+ #speed = random.uniform(0.6, 1.6) # 느린 물고기 / 빠른 물고기
+ #phase = random.uniform(0, 6.28)
+ duration = random.uniform(8, 18) # 전체 이동 주기
+
+ # === 이동 keyframes ===
+ keyframes = f"""
+ @keyframes move-{fish_id} {{
+ 0% {{
+ transform: translate({start_x}px, {start_y}px);
+ }}
+ 25% {{
+ transform: translate({start_x + amplitude_x}px, {start_y + amplitude_y}px);
+ }}
+ 50% {{
+ transform: translate({start_x}px, {start_y - amplitude_y}px) scale(-1,1);
+ }}
+ 75% {{
+ transform: translate({start_x - amplitude_x}px, {start_y + amplitude_y}px) scale(-1,1);
+ }}
+ 100% {{
+ transform: translate({start_x}px, {start_y}px);
+ }}
+ }}
+ """
+
+ # === flip reverse keyframes ===
+ reverse_keyframes = f"""
+ @keyframes keep-label-upright-{fish_id} {{
+ 0%,25% {{
+ transform: scale(1,1);
+ }}
+ 50%,75% {{
+ transform: scale(-1,1); /* 물고기가 뒤집힐 때 라벨도 같이 뒤집혀서 정방향 유지 */
+ }}
+ 100% {{
+ transform: scale(1,1);
+ }}
+ }}
+ """
+
+ return f"""
+
+
+
+
+
+ {inner}
+
+
+
+
+
+ {label_text}
+
+
+
+
+ """
\ No newline at end of file
diff --git a/apps/aquatics/renderer/tank.py b/apps/aquatics/renderer/tank.py
new file mode 100644
index 0000000..2ef85c2
--- /dev/null
+++ b/apps/aquatics/renderer/tank.py
@@ -0,0 +1,69 @@
+from apps.items.models import FishSpecies
+from apps.aquatics.models import Aquarium,ContributionFish , Fishtank
+from .sprite import render_fish_group
+from .utils import strip_outer_svg , extract_svg_size
+
+def render_aquarium_svg(user,width=512, height=512):
+ aquarium = Aquarium.objects.get(user=user)
+
+ if aquarium.background:
+ bg_svg = aquarium.background.background.svg_template
+ else:
+ bg_svg = ''
+
+ width, height = extract_svg_size(bg_svg)
+ bg_inner = strip_outer_svg(bg_svg)
+
+ fishes = ContributionFish.objects.filter(
+ aquarium=aquarium,
+ is_visible=True
+ ).select_related("fish_species", "contributor__repository")
+
+ fish_groups = [
+ render_fish_group(cf, width, height, mode="aquarium")
+ for cf in fishes
+ ]
+
+ return f"""
+
+ """
+
+
+def render_fishtank_svg(repository):
+ fishtank = Fishtank.objects.get(repository=repository)
+
+
+ setting = fishtank.settings.select_related("background__background").first()
+ if setting and setting.background:
+ bg_svg = setting.background.background.svg_template
+ else:
+ bg_svg = ''
+
+
+ width, height = extract_svg_size(bg_svg)
+ bg_inner = strip_outer_svg(bg_svg)
+
+ fishes = ContributionFish.objects.filter(
+ contributor__repository=repository,
+ is_visible=True
+ ).select_related("fish_species", "contributor__user")
+
+ fish_groups = [
+ render_fish_group(cf, width, height, mode="fishtank")
+ for cf in fishes
+ ]
+
+ return f"""
+
+ """
\ No newline at end of file
diff --git a/apps/aquatics/renderer/utils.py b/apps/aquatics/renderer/utils.py
new file mode 100644
index 0000000..5ebeb4e
--- /dev/null
+++ b/apps/aquatics/renderer/utils.py
@@ -0,0 +1,52 @@
+import re
+
+def extract_svg_size(svg_text: str):
+ """
+ Extract width and height from