Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
abd3892
[feat]: photos 앱 기본 구조 및 공통 기반 설정 및 사진 업로드 처리용 서비스 로직 구현
ParkJungHae Jan 28, 2026
030649f
chore: Photo 모델 설계 및 초기 마이그레이션
na0k1m Jan 29, 2026
367eaef
feat: 구글 소셜 로그인 연동 및 의존성 패키지 추가
na0k1m Jan 29, 2026
0cb2abb
docs: requirements.txt 수정
na0k1m Jan 29, 2026
f1f9d97
feat: 이미지 기본 북마크, 메모 기능 구현
na0k1m Jan 30, 2026
d16cfea
feat: 휴지통 자동화 명령어 구현
na0k1m Jan 30, 2026
ecc6363
feat: 기본 html 구현
na0k1m Jan 30, 2026
da37917
[feat] 구글 연동 & Import Job API 구현
hhd517 Jan 30, 2026
d165961
feat: 뷰파일 분리, 모델 수정
na0k1m Jan 30, 2026
b0a2705
feat: 카테고리 대분류 구현
na0k1m Jan 30, 2026
1ddec1b
fix: 북마크 추가 버그 수정
na0k1m Jan 30, 2026
2d0866c
feat: 휴지통 기능 구현
na0k1m Jan 30, 2026
63aeefd
feat: 로그아웃 구현
na0k1m Jan 30, 2026
3b54908
refactor: js 파일 분리
na0k1m Jan 30, 2026
c0e48fa
[feat]: model 구조 수정
ParkJungHae Jan 30, 2026
1cf3c98
[feat] 사진 업로드·조회·중복제거 등 photo app 기능 전반 구현
ParkJungHae Jan 31, 2026
81fa3cb
feat: 세부 폴더 기능 구현
na0k1m Feb 1, 2026
ddcb3e9
[feat] photos앱 사진 수집 & 중복 제거 구현
hhd517 Feb 1, 2026
16bcc0d
merge feat/ny and resolve conflicts
hhd517 Feb 1, 2026
38a5d50
[fix] 로그인/회원가입 플로우 정리 및 갤러리 리다이렉트 수정
hhd517 Feb 1, 2026
42b5150
[fix] 홈 접속 시 로그인 페이지로 리다이렉트 오류 수정
hhd517 Feb 1, 2026
427a7a9
feat: 깃이그노어 static 삭제 및 js 파일 추가
na0k1m Feb 1, 2026
f0f09bc
fix: feat/hd feat/ny 충돌 해결
na0k1m Feb 1, 2026
aa1b9d9
fix: photos<->gallery 모델 충돌 해결
na0k1m Feb 1, 2026
8f7a551
merge: feat/ny2 into feat/hd-test1
hhd517 Feb 2, 2026
96fbf3f
[fix]: 소셜로그인 수정
hhd517 Feb 2, 2026
2af7b48
feat: 구글 포토 불러오기 및 분류 전 카테고리 기능 구현
hhd517 Feb 2, 2026
a40b9d7
[refactor] 구글 포토 불러오기 Picker API 기반으로 구조 변경
hhd517 Feb 5, 2026
d548f7f
[fix] 구글 포토 import 이미지 깨짐 현상 수정
hhd517 Feb 5, 2026
67bb85e
[chore] .env 기반 환경변수 설정 정리 및 시크릿 키 분리
hhd517 Feb 5, 2026
9188510
fix: 대분류 카테고리 4개로 축소
na0k1m Feb 6, 2026
a6ac517
feat: css 추가
na0k1m Feb 6, 2026
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
26 changes: 23 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
# 파이썬 캐시
__pycache__/
*.py[cod]
*.pyc

# 가상환경
.conda/
env/
venv/

# 데이터베이스
db.sqlite3

# 미디어 및 정적 파일 폴더
media/
static/
storage/

# VS Code 설정
.vscode/
# 보안 및 환경 설정 파일 (추가)

# 보안 및 환경 설정 파일
.env
.env.example
credentials.json
token.json

# Celery
celerybeat-schedule

# 로그
*.log

# OS
.DS_Store
Thumbs.db

# cookie
cookies.txt
cookie.txt
90 changes: 90 additions & 0 deletions accounts/static/css/mypage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* 마이페이지 전체 컨테이너 */
.mypage-container {
max-width: 600px;
margin: 50px auto;
background: #fff;
padding: 40px;
border-radius: 24px;
box-shadow: 0 10px 40px rgba(0,0,0,0.06);
text-align: center;
}

.mypage-container h2 {
font-size: 28px;
margin-bottom: 30px;
color: #1a1a1a;
font-weight: 800;
}

/* 유저 아바타(가상) 및 이름 섹션 */
.user-profile-header {
margin-bottom: 30px;
}

.avatar-circle {
width: 80px;
height: 80px;
background: #e7f1ff;
color: #007bff;
font-size: 32px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
margin: 0 auto 15px;
}

/* 유저 상세 정보 목록 */
.user-info-list {
text-align: left;
background: #f8f9fa;
padding: 25px;
border-radius: 16px;
margin-bottom: 30px;
}

.info-row {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #eee;
}

.info-row:last-child {
border-bottom: none;
}

.info-label {
color: #888;
font-weight: 500;
font-size: 14px;
}

.info-value {
color: #333;
font-weight: 600;
}

/* 로그아웃 버튼 */
.logout-wrapper {
margin-top: 20px;
}

.btn-logout {
display: inline-block;
padding: 12px 30px;
color: #ff4757;
background: #fff5f5;
border-radius: 12px;
font-weight: 600;
transition: all 0.2s;
border: 1px solid #ffe0e0;
}

.btn-logout:hover {
background: #ff4757;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 71, 87, 0.2);
}
38 changes: 38 additions & 0 deletions accounts/templates/accounts/mypage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% load static %}

{% block content %}
<link rel="stylesheet" href="{% static 'css/mypage.css' %}">

<div class="mypage-container">
<div class="user-profile-header">
<div class="avatar-circle">
{{ user.username|slice:":1"|upper }}
</div>
<h2>👤 마이페이지</h2>
</div>

<div class="user-info-list">
<div class="info-row">
<span class="info-label">이름</span>
<span class="info-value">{{ user.username }}</span>
</div>
<div class="info-row">
<span class="info-label">이메일</span>
<span class="info-value">{{ user.email|default:"이메일 정보 없음" }}</span>
</div>
<div class="info-row">
<span class="info-label">가입일</span>
<span class="info-value">{{ user.date_joined|date:"Y-m-d" }}</span>
</div>
</div>

<div class="logout-wrapper">
<a href="{% url 'accounts:logout' %}"
class="btn-logout"
onclick="return confirm('로그아웃 하시겠습니까?')">
🚪 로그아웃 하기
</a>
</div>
</div>
{% endblock %}
9 changes: 9 additions & 0 deletions accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
path('mypage/', views.mypage, name='mypage'),
path('logout/', views.logout_view, name='logout'),
]
14 changes: 12 additions & 2 deletions accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from django.shortcuts import render
from django.shortcuts import render, redirect
from django.contrib.auth import logout as auth_logout
from django.contrib.auth.decorators import login_required

# Create your views here.
@login_required
def mypage(request):
# 내 정보 조회
return render(request, 'accounts/mypage.html', {'user': request.user})

def logout_view(request):
# 로그아웃 처리
auth_logout(request)
return redirect('gallery:photo_list')
4 changes: 4 additions & 0 deletions config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# config/__init__.py
from .celery import app as celery_app

__all__ = ('celery_app',)
19 changes: 19 additions & 0 deletions config/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# config/celery.py
import os
from celery import Celery

# Django 설정 모듈 지정
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

# Celery 앱 생성
app = Celery('config')

# Django settings에서 설정 가져오기
app.config_from_object('django.conf:settings', namespace='CELERY')

# Django 앱들에서 tasks.py 자동 발견
app.autodiscover_tasks()

@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')
83 changes: 80 additions & 3 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
"""

from pathlib import Path
import os
from dotenv import load_dotenv

# .env 파일 로드
load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand All @@ -20,12 +25,14 @@
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-m#wcj!^3&hz&v3gz-z%yvxgng2_@r!8cg=c#i3&pt@+rt59=mh'
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY가 설정되지 않았습니다. .env 파일을 확인하세요.")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv('DEBUG', 'True') == 'True'

ALLOWED_HOSTS = []
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')


# Application definition
Expand All @@ -37,12 +44,31 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

# Third party apps
'rest_framework',
'rest_framework.authtoken',

'django.contrib.sites',

'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',

# Local apps
'accounts',
'photos',
'classification',
'gallery',
]

SITE_ID = 1

# 로그인 후 리다이렉트 경로
LOGIN_REDIRECT_URL = '/gallery/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/accounts/login/'

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand All @@ -51,6 +77,7 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
]

ROOT_URLCONF = 'config.urls'
Expand All @@ -66,6 +93,7 @@
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'gallery.context_processors.notification_context',
],
},
},
Expand Down Expand Up @@ -121,7 +149,56 @@

STATIC_URL = 'static/'

STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]

# Media files
MEDIA_URL = os.getenv('MEDIA_URL', '/media/')
MEDIA_ROOT = BASE_DIR / 'media'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# REST Framework
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}

# Authentication
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)

# Google OAuth (로그인용)
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
GOOGLE_SECRET = os.getenv('GOOGLE_SECRET')

SOCIALACCOUNT_PROVIDERS = {
"google": {
"APP": {
"client_id": os.getenv("GOOGLE_CLIENT_ID", ""),
"secret": os.getenv("GOOGLE_SECRET", ""),
"key": "",
},
"SCOPE": ["profile", "email"],
}
}

# Celery 설정
CELERY_BROKER_URL = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
CELERY_RESULT_BACKEND = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Seoul'
19 changes: 18 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,25 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from django.shortcuts import redirect
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
path('', lambda request: redirect('/accounts/login/')),

path('admin/', admin.site.urls),

# auth
path('accounts/', include('accounts.urls')),
path('accounts/', include('allauth.urls')),

# apps
path('gallery/', include('gallery.urls')),
path('api/v1/photos/', include('photos.urls')),
]

if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Loading