From 6dd3d3ff6f8a460432ae1d7f45570b8fc4da7427 Mon Sep 17 00:00:00 2001 From: ewhayein Date: Mon, 12 May 2025 20:27:10 +0900 Subject: [PATCH 1/3] feat: add HTML template rendering with index.html --- app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index a21bd0e..665d28d 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,14 @@ # Hello World Flask 애플리케이션 예제 -from flask import Flask # Flask 모듈 임포트 +from flask import Flask, render_template # Flask 모듈 임포트 # Flask는 파이썬으로 작성된 웹 프레임워크로, 웹 애플리케이션을 쉽게 만들 수 있도록 도와줍니다. app = Flask(__name__) # Flask 애플리케이션 객체 생성 @app.route("/") # 루트 URL에 대한 라우팅 설정 -def hello(): - return "Hello, World!" # 브라우저에 출력될 텍스트 +def home(): + return render_template("index_html") # 브라우저에 출력될 텍스트 if __name__ == "__main__": app.run(debug=True) # 개발 서버 실행 (디버그 모드) From af6b810d2eb44be177c93128e7ddffadaa3d796e Mon Sep 17 00:00:00 2001 From: ewhayein Date: Mon, 12 May 2025 20:42:44 +0900 Subject: [PATCH 2/3] feat: add template rendering with index.html --- template/index.html | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 template/index.html diff --git a/template/index.html b/template/index.html new file mode 100644 index 0000000..ad60a6e --- /dev/null +++ b/template/index.html @@ -0,0 +1,10 @@ + + + + 홈페이지 + + +

Hello, Flask!

+

수정자: 서예인

+ + From a4cc95d831c58570ca0045af8175ea81a20ec6cf Mon Sep 17 00:00:00 2001 From: seoyein Date: Mon, 26 May 2025 21:34:41 +0900 Subject: [PATCH 3/3] Add files via upload --- clubsite/__pycache__/config.cpython-313.pyc | Bin 0 -> 550 bytes clubsite/app/__init__.py | 76 ++++++ .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 3731 bytes .../app/__pycache__/models.cpython-313.pyc | Bin 0 -> 6391 bytes clubsite/app/decorators.py | 84 +++++++ clubsite/app/models.py | 81 ++++++ .../routes/__pycache__/admin.cpython-313.pyc | Bin 0 -> 12274 bytes .../routes/__pycache__/auth.cpython-313.pyc | Bin 0 -> 5318 bytes .../routes/__pycache__/main.cpython-313.pyc | Bin 0 -> 1615 bytes clubsite/app/routes/admin.py | 237 ++++++++++++++++++ clubsite/app/routes/auth.py | 97 +++++++ clubsite/app/routes/main.py | 24 ++ clubsite/app/static/data/categories.csv | 3 + clubsite/app/static/data/groups.csv | 14 ++ clubsite/app/static/data/members.csv | 40 +++ clubsite/app/static/data/users.csv | 41 +++ clubsite/app/static/index.css | 48 ++++ clubsite/app/static/index0.css | 31 +++ clubsite/app/static/login.css | 50 ++++ clubsite/app/static/signup.css | 77 ++++++ clubsite/app/templates/auth-check.js | 22 ++ clubsite/app/templates/auth/login.html | 65 +++++ clubsite/app/templates/auth/logout.html | 17 ++ clubsite/app/templates/auth/signup.html | 108 ++++++++ clubsite/app/templates/category/beginner.html | 18 ++ .../app/templates/category/challenger.html | 15 ++ clubsite/app/templates/errors/403.html | 15 ++ clubsite/app/templates/errors/404.html | 15 ++ clubsite/app/templates/errors/500.html | 15 ++ clubsite/app/templates/index.html | 52 ++++ clubsite/app/templates/index0.html | 16 ++ clubsite/app/templates/team/group.html | 14 ++ clubsite/app/templates/team/member.html | 16 ++ clubsite/app/templates/unauthorized.html | 36 +++ clubsite/config.py | 6 + clubsite/flaskenv | 3 + clubsite/instance/club.db | Bin 0 -> 28672 bytes clubsite/requirements.txt | Bin 0 -> 304 bytes clubsite/run.py | 37 +++ clubsite/seed.py | 55 ++++ 40 files changed, 1428 insertions(+) create mode 100644 clubsite/__pycache__/config.cpython-313.pyc create mode 100644 clubsite/app/__init__.py create mode 100644 clubsite/app/__pycache__/__init__.cpython-313.pyc create mode 100644 clubsite/app/__pycache__/models.cpython-313.pyc create mode 100644 clubsite/app/decorators.py create mode 100644 clubsite/app/models.py create mode 100644 clubsite/app/routes/__pycache__/admin.cpython-313.pyc create mode 100644 clubsite/app/routes/__pycache__/auth.cpython-313.pyc create mode 100644 clubsite/app/routes/__pycache__/main.cpython-313.pyc create mode 100644 clubsite/app/routes/admin.py create mode 100644 clubsite/app/routes/auth.py create mode 100644 clubsite/app/routes/main.py create mode 100644 clubsite/app/static/data/categories.csv create mode 100644 clubsite/app/static/data/groups.csv create mode 100644 clubsite/app/static/data/members.csv create mode 100644 clubsite/app/static/data/users.csv create mode 100644 clubsite/app/static/index.css create mode 100644 clubsite/app/static/index0.css create mode 100644 clubsite/app/static/login.css create mode 100644 clubsite/app/static/signup.css create mode 100644 clubsite/app/templates/auth-check.js create mode 100644 clubsite/app/templates/auth/login.html create mode 100644 clubsite/app/templates/auth/logout.html create mode 100644 clubsite/app/templates/auth/signup.html create mode 100644 clubsite/app/templates/category/beginner.html create mode 100644 clubsite/app/templates/category/challenger.html create mode 100644 clubsite/app/templates/errors/403.html create mode 100644 clubsite/app/templates/errors/404.html create mode 100644 clubsite/app/templates/errors/500.html create mode 100644 clubsite/app/templates/index.html create mode 100644 clubsite/app/templates/index0.html create mode 100644 clubsite/app/templates/team/group.html create mode 100644 clubsite/app/templates/team/member.html create mode 100644 clubsite/app/templates/unauthorized.html create mode 100644 clubsite/config.py create mode 100644 clubsite/flaskenv create mode 100644 clubsite/instance/club.db create mode 100644 clubsite/requirements.txt create mode 100644 clubsite/run.py create mode 100644 clubsite/seed.py diff --git a/clubsite/__pycache__/config.cpython-313.pyc b/clubsite/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3145d62b6c8f42fc0ab4fd850178ec4894bbe4c9 GIT binary patch literal 550 zcmYjN%SyvQ6rD+8jKw1LfiEZ`xTr#lqMIT@j1g;V#ZFxag-ai44W_A1QbAX)`~X4p z6LjIf2yV)_bYpj77jB(Ntn|X%Irnw$qZf@v5SP!n_0t#re+Z*5${*-0aBzq8&?gK{a39k)!wYQDNp52PJW=;0V~pR?$d4l9m2bpVQ1c7N Cv4U9u literal 0 HcmV?d00001 diff --git a/clubsite/app/__init__.py b/clubsite/app/__init__.py new file mode 100644 index 0000000..76fdd33 --- /dev/null +++ b/clubsite/app/__init__.py @@ -0,0 +1,76 @@ +# app/__init__.py +from flask import Flask, render_template +from flask_login import LoginManager, current_user, login_required +from config import Config + +login_manager = LoginManager() + +@login_manager.user_loader +def load_user(user_id): + # 함수 내부에서 import하여 순환 import 방지 + from app.models import User + return User.query.get(int(user_id)) + +def create_app(): + app = Flask(__name__) + app.config.from_object(Config) + + # DB 초기화 (늦은 import) + from app.models import db + db.init_app(app) + + # 로그인 매니저 초기화 + login_manager.init_app(app) + login_manager.login_view = 'auth.login' + login_manager.login_message = '로그인이 필요합니다.' + login_manager.login_message_category = 'info' + + @app.context_processor + def inject_user(): + return { + 'current_user': current_user, + 'is_admin': current_user.is_authenticated and current_user.is_admin_user() + } + + # 블루프린트 등록 (늦은 import) + from app.routes.auth import auth_bp + from app.routes.admin import admin_bp + + app.register_blueprint(auth_bp, url_prefix='/auth') + app.register_blueprint(admin_bp, url_prefix='/admin') + + # 메인 라우트 + from flask import Blueprint + main_bp = Blueprint('main', __name__) + + @main_bp.route('/') + def index(): + return render_template('index0.html', title='동아리 홈페이지') + + @main_bp.route('/dashboard') + @login_required + def dashboard(): + user_teams = current_user.get_teams() + return render_template('index.html', + title='대시보드', + teams=user_teams) + + + app.register_blueprint(main_bp) + + # 에러 핸들러 + @app.errorhandler(403) + def forbidden(error): + return render_template('errors/403.html'), 403 + + @app.errorhandler(404) + def not_found(error): + return render_template('errors/404.html'), 404 + + @app.errorhandler(500) + def internal_error(error): + from app.models import db # 함수 내부에서 import + db.session.rollback() + return render_template('errors/500.html'), 500 + + return app \ No newline at end of file diff --git a/clubsite/app/__pycache__/__init__.cpython-313.pyc b/clubsite/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f143fba2f2f94a584c896aaa18cf31c4de85eea GIT binary patch literal 3731 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' + +class Group(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) + members = db.relationship('Member', backref='group', lazy=True) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def get_member_count(self): + """그룹 멤버 수 반환""" + return len(self.members) + + def has_member(self, user_id): + """특정 사용자가 이 그룹의 멤버인지 확인""" + return any(member.user_id == user_id for member in self.members if member.user_id) + + def __repr__(self): + return f'' + +class Member(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + department = db.Column(db.String(150)) + blog_url = db.Column(db.String(200)) + group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) \ No newline at end of file diff --git a/clubsite/app/routes/__pycache__/admin.cpython-313.pyc b/clubsite/app/routes/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaa28ac130c6e1cf5ccea0ee585445d0ff115583 GIT binary patch literal 12274 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 literal 0 HcmV?d00001 diff --git a/clubsite/app/routes/__pycache__/auth.cpython-313.pyc b/clubsite/app/routes/__pycache__/auth.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3873fd66b184d438d2e2e43fc8626a053c74661 GIT binary patch literal 5318 zcmc&&Z){W76~E7Zwx9iMKL>}zBu!%o8F3*=p#(unS!qKdrWw=^x1o;kocKXJ{3pB5 zR!b&Tum&|iB~q0LLrI&e?G`Gep^0g!#0U0eYrj2XOGZzb+G)|IVMhvs4-=nu?t6K* z^M}!)Rl7>w{ddl}=l(h8{EqM2Y!(D%cf;1f!yH0?B^|9878>_;6hh;OM?AG3c_~bJ zby(-sW4)KgwAX+QUL!WT$-HjA$;)8IYsO};1zWrVA5!KN;^8OUXKEdHtSQKm)28j zUf0=+&|WIE6(p7^t&6Eh&j(L(Y2o$SI`Jprrc2yZph}bd1X>t0Ia(dp1y*VDd~jAv zi?4R+2tt0u8+TB?HOptThZooT^`#pa0}j2femS+jbO$BcVL00cjk0$OZ`z@A6YfTB ztXykrm3Z5Lcda(hGr+Y@>y>Le;?2+Ld5dPc?v-B$tDTQl!|N!^w*G*!ZM?lgw~|xg zIdD{t9lG_%QO+;>7f@B^HXr+ah4lZ)0+oGD9ph9s(0%hC-tFRsx5K zUB`r2zksh&GEL?@6ed@j+76$)H%=CQb*=dE7zlNj1Q*8A#fziGv%f2x8!epsyv;p# zDP2sTnj62W4s3F#Xrjou^jg^@rUnKCQ5?1_%7npT^d~|%oZ3hzG*!Qc#9csN{N=Uc z<*P)UFHgG*>9NAu3+}>KBZccH3%?zmOHbdue$rh`Un^X{Q263T@z>B9KU-E)HV7Ce z@GwW*i|Sn329vQUY}}BHB%^}IiHXt5j))i}G~lQr1K^ax*a*XzxCl%XfTN6HaG~~rL1Ex{P&;}|ZcDb2#lo=# z_lb%Bz{G$sIYJMOSCe)P`H)+YX>x@knR7#tpMJTsZy=iL7b8ibFEljNhr#y>Vjoez zZ3tFYd?bz+^{LOH+S4@-EtgAK4wMk6dtjYe~JEz`B*W^r% zGtRn;J3rW&cQ$35O_ScNvwf6Vc?@KHV!dR|yIL}?mZ`d|YirioG0Mzx&hx`(hVxug zhHIK~NnDe}ZOL=nGu-wp*Evf6jpfF=^3@wM)f=XmeD&r`_2wCC%~-#h8ixU0feK;{R zY;MQGv4{v)xhUw6*PsNQf5K(_--3=V1Oe#@-2MM9=#U4i6yNs^c%@JSA?Cm5HfoMH zu73hvhOa^Hti7_?pTyT1z{PxFJviD}Lms{082+LmxN#jlK;UZMpXAv^N3}KzjCqa zF_4HuFpQOgplt4s;N&2|{wbnd;mfJQhm-EP@sYxZR|;R;bc5i+S_$N*u>e!ElP)(=tpjakKbUdTu&)9&J@ zH;Nx$15$!5D)9!>-o@c-3%@JE2RI6K#+uu7HYc zSyx+@YnPbz$D$%f?M3Sc*1W@=akwXIvkuRwVMU}=zwVQrmv-jsH)iTLN}G0N>whdU zt{GQj1$wH_x;jQ}0MQ)Iv3--hw;CkpfjoCG!yTLz>7{&KqfS4>jX*=UtxLF3(i!G~mLnysJCo>dw0M zk9I$>0>ryhwdT&M+B-Fk_e`|IGD^=|klB`J)?}D9>6RSRG|O1dTh3U{+t1j?k~!w7 z8MUuc7d{{Y$9m`AnZj*ka%PT3BC>uK!O0V)l%Wj z$-Gi{iq}2Ho0qUz*_(T4#oI1e?l?G%cT>dMRSqn*bD56BAwMB83epIGiAXikQZ929 zVUmQYR$){(hDn-5en1h4>4Hr#$lw_1nW4>+rDf_zIePO9>%6cx-IJ|;Cd+P==#7eu zpLlB2UxSWgvQs5@QBtJ}$3dW;%#xpsjtAAOgk&VDVk~JXxt9f!n6_9VEJQ`c38>CQ z^&Y_>$xnkp*+jfluzyH4DCv%p-YC|rsE>&+tk@c}6Xd0$!Y;fACdloGrTL`kJBoR);Z(!u+mp>xHIt1e8zgG;J>42= zH+5f+bX(??=KMBzQwrHHCjC>dOdcR)Tkq+fqxL9d9rH?a{%4d%cOpxzxkGXHbTqYz P2y&25-8~w*irW7MF#fFB literal 0 HcmV?d00001 diff --git a/clubsite/app/routes/__pycache__/main.cpython-313.pyc b/clubsite/app/routes/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a590084da6024f33ee4aba543e6992b102d32e08 GIT binary patch literal 1615 zcmd5+&2JM&6rb5$+v{ELnh+AIP>>bO#|q@AhX6@T4y1z0QFDkDE?#M6$3wDW{lTnF z6`#zhfn zL$K~HTsiztLg;UkexBG;AZ7SXc0oKtHDTmpHzqAHa2rQ|u%;xn zQ7tpQs^UE;&6-q1mb^1Cn|1%N(W%pB>9E)GUSg*0M=83*w-lNNH&d*ACfe78sJdbexC3wy-3`O`|>BbH6RMd!emXXF<_4AHwZ zI=S@gxnItW=gx<7=SOoFhLzv-rQpn!*IKA=1f&5+`@GQU=b~L37;Z=6>8E z9D@?lJWxJ|G@u8s;oXEV5mktUPF%}QtyO6cS7kw`vI8~Orx`+G1O0#(*%#EpT{=>& z6y?FG34&$sxn2+2rg22`+{pQb#!5#|qYI$=yxN5VJpmijAG+iRCkjNr0*k>B-+&M_ zn)6ROx)bgvtEEQ}3}i zHjANId~q0<#lXBiHa9|ZV`SbO-r-DczPcXj8-cPRnB1}QEOpPT`}ZjyT2V=+3xgy6 z2qNxFsi#QI;@R@NMU6v9vB<4j_TO|4ti4R5%p_U<7 z-y@rp&k~;m{|Sg=8DsngUHt=He1l3MD*dI+|9t!TcO&h>F!Lu?e?0r>>`(p(&;N$= zZzTzTizhOYPH=JRSmFAyili)u1^G)Od^yIgB`}M_)@D!_NBB~VDkrGRZvj;n`Th^w CutS9a literal 0 HcmV?d00001 diff --git a/clubsite/app/routes/admin.py b/clubsite/app/routes/admin.py new file mode 100644 index 0000000..7bd02ee --- /dev/null +++ b/clubsite/app/routes/admin.py @@ -0,0 +1,237 @@ +# app/routes/admin.py +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify +from flask_login import current_user +from sqlalchemy import func + +admin_bp = Blueprint('admin', __name__) + +@admin_bp.route('/dashboard') +def dashboard(): + """관리자 대시보드""" + # 함수 내부에서 import하여 순환 import 방지 + from app.models import db, User, Group, Member, Category + from app.decorators import admin_required + + # 데코레이터 수동 적용 + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + # 통계 정보 수집 + total_users = User.query.count() + total_groups = Group.query.count() + total_members = Member.query.count() + + # 카테고리별 그룹 수 + group_stats = (db.session.query(Category.name, func.count(Group.id).label('count')) + .join(Group, Category.id == Group.category_id) + .group_by(Category.name).all()) + + # 최근 가입한 사용자들 + recent_users = User.query.order_by(User.created_at.desc()).limit(5).all() + + return render_template('admin/dashboard.html', + title='관리자 대시보드', + total_users=total_users, + total_groups=total_groups, + total_members=total_members, + group_stats=group_stats, + recent_users=recent_users) + +@admin_bp.route('/users') +def manage_users(): + """사용자 관리 페이지""" + from app.models import User + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + page = request.args.get('page', 1, type=int) + per_page = 20 + + users = User.query.paginate( + page=page, per_page=per_page, error_out=False + ) + + return render_template('admin/users.html', + title='사용자 관리', + users=users) + +@admin_bp.route('/users//admin-toggle', methods=['POST']) +def toggle_user_admin(user_id): + """사용자 관리자 권한 토글""" + from app.models import db, User + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + user = User.query.get_or_404(user_id) + + # 자기 자신의 권한은 변경할 수 없음 + if user.id == current_user.id: + flash('자신의 권한은 변경할 수 없습니다.', 'error') + return redirect(url_for('admin.manage_users')) + + old_status = '관리자' if user.is_admin else '일반사용자' + user.is_admin = not user.is_admin + new_status = '관리자' if user.is_admin else '일반사용자' + + db.session.commit() + + flash(f'{user.name}의 권한이 {old_status}에서 {new_status}로 변경되었습니다.', 'success') + return redirect(url_for('admin.manage_users')) + +@admin_bp.route('/users//delete', methods=['POST']) +def delete_user(user_id): + """사용자 삭제""" + from app.models import db, User + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + user = User.query.get_or_404(user_id) + + # 자기 자신은 삭제할 수 없음 + if user.id == current_user.id: + flash('자신의 계정은 삭제할 수 없습니다.', 'error') + return redirect(url_for('admin.manage_users')) + + username = user.name + + db.session.delete(user) + db.session.commit() + + flash(f'{username} 사용자가 삭제되었습니다.', 'info') + return redirect(url_for('admin.manage_users')) + +@admin_bp.route('/groups') +def manage_groups(): + """그룹 관리 페이지""" + from app.models import Group + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + groups = Group.query.all() + + return render_template('admin/groups.html', + title='그룹 관리', + groups=groups) + +@admin_bp.route('/groups/') +def group_admin_detail(group_id): + """관리자용 그룹 상세 정보""" + from app.models import Group, Member + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + group = Group.query.get_or_404(group_id) + members = Member.query.filter_by(group_id=group_id).all() + + return render_template('admin/group_detail.html', + title=f'{group.name} 그룹 관리', + group=group, + members=members) + +@admin_bp.route('/groups/create', methods=['GET', 'POST']) +def create_group(): + """새 그룹 생성""" + from app.models import db, Group, Category + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('login')) + + if request.method == 'POST': + name = request.form.get('name') + category_id = request.form.get('category_id') + + if not name or not category_id: + flash('그룹 이름과 카테고리를 입력해주세요.', 'error') + categories = Category.query.all() + return render_template('admin/create_group.html', + title='새 그룹 생성', + categories=categories) + + # 중복 확인 + if Group.query.filter_by(name=name).first(): + flash('이미 존재하는 그룹명입니다.', 'error') + categories = Category.query.all() + return render_template('admin/create_group.html', + title='새 그룹 생성', + categories=categories) + + new_group = Group(name=name, category_id=category_id) + db.session.add(new_group) + db.session.commit() + + flash(f'{name} 그룹이 생성되었습니다.', 'success') + return redirect(url_for('admin.manage_groups')) + + categories = Category.query.all() + return render_template('admin/create_group.html', + title='새 그룹 생성', + categories=categories) + +@admin_bp.route('/groups//delete', methods=['POST']) +def delete_group(group_id): + """그룹 삭제""" + from app.models import db, Group + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('auth.login')) + + group = Group.query.get_or_404(group_id) + group_name = group.name + + db.session.delete(group) + db.session.commit() + + flash(f'{group_name} 그룹이 삭제되었습니다.', 'info') + return redirect(url_for('admin.manage_groups')) + +@admin_bp.route('/all-groups-data') +def all_groups_data(): + """모든 그룹의 상세 정보 (관리자 전용)""" + from app.models import Group + + if not current_user.is_authenticated or not current_user.is_admin_user(): + flash('관리자 권한이 필요합니다.', 'error') + return redirect(url_for('login')) + + groups = Group.query.all() + group_data = [] + + for group in groups: + members_data = [] + for member in group.members: + member_info = { + 'name': member.name, + 'department': member.department or '미설정', + 'blog_url': member.blog_url or '없음' + } + if member.user: + member_info.update({ + 'email': member.user.email, + 'joined_at': member.user.created_at.strftime('%Y-%m-%d') + }) + members_data.append(member_info) + + group_data.append({ + 'id': group.id, + 'name': group.name, + 'category': group.category.name, + 'member_count': len(group.members), + 'members': members_data + }) + + return render_template('admin/all_groups_data.html', + title='전체 그룹 데이터', + groups_data=group_data) \ No newline at end of file diff --git a/clubsite/app/routes/auth.py b/clubsite/app/routes/auth.py new file mode 100644 index 0000000..873ffe1 --- /dev/null +++ b/clubsite/app/routes/auth.py @@ -0,0 +1,97 @@ +# app/routes/auth.py +from flask import Blueprint, render_template, request, redirect, url_for, flash +from flask_login import login_user, logout_user, login_required, current_user +from app.models import db, User +from datetime import datetime + +auth_bp = Blueprint('auth', __name__) + +@auth_bp.route('/login', methods=['GET', 'POST']) +def login(): + """로그인""" + if current_user.is_authenticated: + return redirect(url_for('main.index')) + + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + remember = bool(request.form.get('remember')) + + user = User.query.filter_by(name=username).first() # 수정: name 필드 사용 + + if user and user.check_password(password): + login_user(user, remember=remember) + + # 관리자라면 관리자 대시보드로, 일반 사용자라면 일반 대시보드로 + if user.is_admin_user(): # 수정: 올바른 메서드명 + flash(f'관리자로 로그인되었습니다. 환영합니다, {user.name}님!', 'success') + return redirect(url_for('main.dashboard')) + else: + flash(f'로그인되었습니다. 환영합니다, {user.name}님!', 'success') + return redirect(url_for('main.dashboard')) + else: + flash('사용자명 또는 비밀번호가 올바르지 않습니다.', 'error') + + return render_template('auth/login.html', title='로그인') + +@auth_bp.route('/register', methods=['GET', 'POST']) +def register(): + """회원가입""" + if current_user.is_authenticated: + return redirect(url_for('main.index0')) + + if request.method == 'POST': + username = request.form.get('username') + email = request.form.get('email') + password = request.form.get('password') + confirm_password = request.form.get('confirm_password') + birthdate_str = request.form.get('birthdate') + + # 유효성 검사 + if not all([username, email, password, confirm_password, birthdate_str]): + flash('모든 필드를 입력해주세요.', 'error') + return render_template('auth/signup.html', title='회원가입') + + if password != confirm_password: + flash('비밀번호가 일치하지 않습니다.', 'error') + return render_template('auth/signup.html', title='회원가입') + + # 중복 확인 + if User.query.filter_by(name=username).first(): + flash('이미 존재하는 사용자명입니다.', 'error') + return render_template('auth/signup.html', title='회원가입') + + if User.query.filter_by(email=email).first(): + flash('이미 존재하는 이메일입니다.', 'error') + return render_template('auth/signup.html', title='회원가입') + + # 생년월일 변환 + try: + birthdate = datetime.strptime(birthdate_str, '%Y-%m-%d').date() + except ValueError: + flash('올바른 생년월일 형식을 입력해주세요. (YYYY-MM-DD)', 'error') + return render_template('auth/signup.html', title='회원가입') + + # 새 사용자 생성 + user = User( + name=username, + email=email, + birthdate=birthdate + ) + user.set_password(password) + + db.session.add(user) + db.session.commit() + + flash('회원가입이 완료되었습니다. 로그인해주세요.', 'success') + return redirect(url_for('auth.login')) + + return render_template('auth/signup.html', title='회원가입') + +@auth_bp.route('/logout') +@login_required +def logout(): + """로그아웃""" + logout_user() + flash('로그아웃되었습니다.', 'info') + return redirect(url_for('main.index0')) \ No newline at end of file diff --git a/clubsite/app/routes/main.py b/clubsite/app/routes/main.py new file mode 100644 index 0000000..340de6d --- /dev/null +++ b/clubsite/app/routes/main.py @@ -0,0 +1,24 @@ +from flask import Blueprint, render_template +from flask_login import login_required +from app.models import Category, Group, Member + +main_bp = Blueprint('main', __name__) + +@main_bp.route('/dashboard') +@login_required +def dashboard(): + categories = Category.query.all() + return render_template('index.html', categories=categories, title = 'main page') + + +@main_bp.route('/category/') +@login_required +def view_groups(cat_id): + groups = Group.query.filter_by(category_id=cat_id).all() + return render_template('groups.html', groups=groups) + +@main_bp.route('/group/') +@login_required +def view_members(group_id): + members = Member.query.filter_by(group_id=group_id).all() + return render_template('members.html', members=members) \ No newline at end of file diff --git a/clubsite/app/static/data/categories.csv b/clubsite/app/static/data/categories.csv new file mode 100644 index 0000000..dcdbceb --- /dev/null +++ b/clubsite/app/static/data/categories.csv @@ -0,0 +1,3 @@ +id,name +1,비기너 +2,챌린저 diff --git a/clubsite/app/static/data/groups.csv b/clubsite/app/static/data/groups.csv new file mode 100644 index 0000000..47cc42e --- /dev/null +++ b/clubsite/app/static/data/groups.csv @@ -0,0 +1,14 @@ +id,name,category_id +1,헬로 보안,1 +2,Drawhat,1 +3,네버해킹,1 +4,ascent,1 +5,과탑,1 +6,Under Construction,2 +7,FӨЯΣПƧΣΣKΣЯƧ,2 +8,𝟺𝟶𝟺𝙿𝚠𝚗𝙵𝚘𝚞𝚗𝚍,2 +9,gₕₒ₃ₜ,2 +10,QRA,2 +11,Get 4 Guitar,2 +12,LuckyCookie,2 +13,ChatSploit,2 diff --git a/clubsite/app/static/data/members.csv b/clubsite/app/static/data/members.csv new file mode 100644 index 0000000..9e7a265 --- /dev/null +++ b/clubsite/app/static/data/members.csv @@ -0,0 +1,40 @@ +id,name,department,group_id,student number,blog_url +1,유예원,사이버보안학과,1,2371089,https://www.notion.so/hwijugn/1b370ca2d94780b9bf91e9eef3d34ce7?v=1b370ca2d9478148a813000c85edb6ff&p=1c670ca2d94780e6a29cdcac5a2e35ea&pm=s +2,김은빈,영어영문학과,1,2090019,https://kimeunnen.tistory.com/ +3,박고은,사이버보안학과,1,2466020,https://pokpung-ganji.tistory.com +4,서연아,컴퓨터공학과,1,2476135,https://docs.google.com/document/d/1mEF5WvuRlFMoNaEUOKpXcFEBN1SecTxMjFEooSPGkls/edit?usp=sharing +5,박주영,사이버보안학과,1,2467012,() +6,공지영,사이버보안학과,2,2467004,https://blog.naver.com/kongjiyeong_ +7,서예인,컴퓨터공학과,2,2466029,https://todont-knowfrom0521.tistory.com/ +8,왕은서,소프트웨어학부,2,2371088,https://itsmeking.tistory.com +9,정채린,사이버보안학과,3,2467026,https://blog.naver.com/kijmhan77/223804993618 +10,황영서,컴퓨터공학과,3,2371069,https://velog.io/@kagu/posts +11,석윤서,사이버보안학과,3,2467018,https://blog.naver.com/ysseok05 +12,민채은,소프트웨어학부,3,2371080,https://velog.io/@alscodms04/posts +13,노윤하,컴퓨터공학과,4,2566014,https://nononnn.tistory.com/5 +14,박지예,사이버보안학과,4,2467013,https://m.blog.naver.com/1001zi +15,송유진,컴퓨터공학과,4,2376138,https://slytherin-1.tistory.com/2 +16,임정민,사이버보안학과,4,2371095,https://blog.naver.com/limjm0424 +17,김은아,소프트웨어학부,5,2371079,https://velog.io/@anb0o/posts +18,백소정,사이버보안학과,5,2467014,https://werty2.tistory.com/ +19,오로라,사이버보안학과,5,2467022,https://velog.io/@aroro6210/posts +20,주현아,컴퓨터공학과,5,2466055,bibimbab03 (비빔) / 작성글 - velog +21,지연경,소프트웨어학부,6,(),() +22,조휘정,사이버보안학과,6,(),https://blog.naver.com/ysseok05/223808413956 +23,박연수,소프트웨어학부,7,(),https://dustn926.tistory.com/category/연구/Deepfake로부터 안전한 사진 변환 서비스 +24,장다연,(),7,(),https://velog.io/@daniayo/series/ECOPS +25,송연우,(),8,(),https://miaami.tistory.com/ +26,최다인,소프트웨어학부,8,(),https://dada42.tistory.com/ +27,곽인정,(),9,(),https://securitystudying.tistory.com/ +28,박시원,(),9,(),() +29,이승연,(),10,(),https://blog.naver.com/seulyeon719 +30,김세정,소프트웨어학부,10,(),https://tpwjdstudy.tistory.com/ +31,장다연,(),11,(),https://velog.io/@daniayo/series/ECOPS +32,조휘정,사이버보안학과,11,(),https://blog.naver.com/ysseok05/223808413956 +33,곽인정,(),11,(),https://dada42.tistory.com/ +34,송연우,(),12,(),https://miaami.tistory.com/ +35,지연경,소프트웨어학부,12,(),() +36,이승연,(),12,(),https://blog.naver.com/seulyeon719 +37,박연수,소프트웨어학부,13,(),https://dustn926.tistory.com/category/연구/Deepfake로부터 안전한 사진 변환 서비스 +38,이승연,(),13,(),https://blog.naver.com/seulyeon719 +39,박시원,(),13,(),() diff --git a/clubsite/app/static/data/users.csv b/clubsite/app/static/data/users.csv new file mode 100644 index 0000000..90e2b33 --- /dev/null +++ b/clubsite/app/static/data/users.csv @@ -0,0 +1,41 @@ +id,name,password,is_admin,email +1,admin,password123,TRUE,123 +2,user1,pass1,FALSE,123 +3,user2,pass2,FALSE,123 +4,user3,pass3,FALSE,123 +5,user4,pass4,FALSE,123 +6,user5,pass5,FALSE,123 +7,user6,pass6,FALSE,123 +8,user7,pass7,FALSE,123 +9,user8,pass8,FALSE,123 +10,user9,pass9,FALSE,123 +11,user10,pass10,FALSE,123 +12,user11,pass11,FALSE,123 +13,user12,pass12,FALSE,123 +14,user13,pass13,FALSE,123 +15,user14,pass14,FALSE,123 +16,user15,pass15,FALSE,123 +17,user16,pass16,FALSE,123 +18,user17,pass17,FALSE,123 +19,user18,pass18,FALSE,123 +20,user19,pass19,FALSE,123 +21,user20,pass20,FALSE,123 +22,user21,pass21,FALSE,123 +23,user22,pass22,FALSE,123 +24,user23,pass23,FALSE,123 +25,user24,pass24,FALSE,123 +26,user25,pass25,FALSE,123 +27,user26,pass26,FALSE,123 +28,user27,pass27,FALSE,123 +29,user28,pass28,FALSE,123 +30,user29,pass29,FALSE,123 +31,user30,pass30,FALSE,123 +32,user31,pass31,FALSE,123 +33,user32,pass32,FALSE,123 +34,user33,pass33,FALSE,123 +35,user34,pass34,FALSE,123 +36,user35,pass35,FALSE,123 +37,user36,pass36,FALSE,123 +38,user37,pass37,FALSE,123 +39,user38,pass38,FALSE,123 +40,user39,pass39,FALSE,123 diff --git a/clubsite/app/static/index.css b/clubsite/app/static/index.css new file mode 100644 index 0000000..1cd1bba --- /dev/null +++ b/clubsite/app/static/index.css @@ -0,0 +1,48 @@ +body { + margin: 0; + padding: 0; + background-color: #000; + color: white; + font-family: 'Segoe UI', sans-serif; + display: flex; + flex-direction: column; + height: 100vh; +} + +header { + background-color: #111; + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +header h1 { + margin: 0; + font-size: 36px; +} + +.auth-links a { + color: white; + text-decoration: none; + margin-left: 15px; +} + +.container { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + gap: 100px; +} + +.category a { + font-size: 32px; + font-weight: bold; + color: #00bfff; + text-decoration: none; +} + +.category a:hover { + color: #1e90ff; +} \ No newline at end of file diff --git a/clubsite/app/static/index0.css b/clubsite/app/static/index0.css new file mode 100644 index 0000000..b1d5e57 --- /dev/null +++ b/clubsite/app/static/index0.css @@ -0,0 +1,31 @@ +body { + margin: 0; + height: 100vh; + background-color: black; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + font-family: sans-serif; +} + +h1 { + font-size: 60px; + margin-bottom: 20px; +} + +.login-btn { + font-size: 20px; + color: white; + border: 2px solid white; + padding: 10px 30px; + background: none; + cursor: pointer; + border-radius: 10px; +} + +.login-btn:hover { + background-color: white; + color: black; +} \ No newline at end of file diff --git a/clubsite/app/static/login.css b/clubsite/app/static/login.css new file mode 100644 index 0000000..4b82599 --- /dev/null +++ b/clubsite/app/static/login.css @@ -0,0 +1,50 @@ +body { + background-color: #121212; + color: #ffffff; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +table { + width: 300px; + background-color: #1e1e1e; + padding: 30px; + border-radius: 12px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.7); + font-size: 15px; +} + +input[type="text"], +input[type="password"] { + width: 100%; + height: 36px; + font-size: 15px; + border: none; + border-radius: 10px; + outline: none; + padding-left: 10px; + background-color: #2a2a2a; + color: white; + margin-bottom: 15px; + box-sizing: border-box; +} + +.btn { + width: 100%; + height: 36px; + font-size: 15px; + border: none; + border-radius: 10px; + background-color: rgb(164, 199, 255); /* 기존 파스텔 블루 */ + color: black; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.btn:active { + background-color: rgb(61, 135, 255); /* 클릭 시 더 진한 블루 */ +} diff --git a/clubsite/app/static/signup.css b/clubsite/app/static/signup.css new file mode 100644 index 0000000..c654790 --- /dev/null +++ b/clubsite/app/static/signup.css @@ -0,0 +1,77 @@ +body { + background-color: #121212; + color: #ffffff; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + display: flex; + justify-content: center; + padding: 40px; +} + +form { + background-color: #1e1e1e; + padding: 30px; + border-radius: 12px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.7); +} + +table { + width: 100%; +} + +h2 { + text-align: center; + margin-bottom: 20px; + color: white; +} + +.text, +.email { + width: 95%; + padding: 10px; + margin: 5px 0; + background-color: #2a2a2a; + color: white; + border: 1px solid #555; + border-radius: 6px; +} + +.email-container { + display: flex; + align-items: center; + gap: 8px; +} + +.at { + color: #bbb; + font-weight: bold; +} + +.check-btn { + padding: 6px 10px; + margin-left: 8px; + background-color: #444; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.check-btn:hover { + background-color: #555; +} + +.btn { + width: 100%; + padding: 12px; + background-color: rgb(164, 199, 255); + color: #000; + font-weight: bold; + border: none; + border-radius: 8px; + cursor: pointer; + margin-top: 20px; +} + +.btn:hover { + background-color: rgb(61, 135, 255); +} diff --git a/clubsite/app/templates/auth-check.js b/clubsite/app/templates/auth-check.js new file mode 100644 index 0000000..0a1fb18 --- /dev/null +++ b/clubsite/app/templates/auth-check.js @@ -0,0 +1,22 @@ +window.onload = function () { + const isLoggedIn = localStorage.getItem('loggedIn'); + if (!isLoggedIn) { + alert("로그인 후 이용 가능합니다."); + + // 절대경로 리디렉션 (항상 ECOPS_webpage 기준으로 이동) + const redirectURL = `${window.location.origin}/ECOPS_webpage/index0.html`; + window.location.href = redirectURL; + return; + } + + // 로그인 상태면 본문 표시 + document.body.style.display = "block"; + + // 새로고침 시 로그아웃 + const navType = performance.getEntriesByType("navigation")[0]?.type; + if (navType === "reload") { + localStorage.removeItem('loggedIn'); + const redirectURL = `${window.location.origin}/ECOPS_webpage/index0.html`; + window.location.href = redirectURL; + } +}; diff --git a/clubsite/app/templates/auth/login.html b/clubsite/app/templates/auth/login.html new file mode 100644 index 0000000..b357ad6 --- /dev/null +++ b/clubsite/app/templates/auth/login.html @@ -0,0 +1,65 @@ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+

로그인

+
로그인 정보 저장
회원가입
+
+ + + \ No newline at end of file diff --git a/clubsite/app/templates/auth/logout.html b/clubsite/app/templates/auth/logout.html new file mode 100644 index 0000000..5ef7e60 --- /dev/null +++ b/clubsite/app/templates/auth/logout.html @@ -0,0 +1,17 @@ + + + + + 로그아웃 + + + + + diff --git a/clubsite/app/templates/auth/signup.html b/clubsite/app/templates/auth/signup.html new file mode 100644 index 0000000..51f4618 --- /dev/null +++ b/clubsite/app/templates/auth/signup.html @@ -0,0 +1,108 @@ + + + + + + + Join + + + + +
+ + + + + + + + + + + + + + + + + +

회원가입

아이디
+
+ + +
+
비밀번호
비밀번호 확인
이름
이메일
+
+ + + + diff --git a/clubsite/app/templates/category/beginner.html b/clubsite/app/templates/category/beginner.html new file mode 100644 index 0000000..feda53a --- /dev/null +++ b/clubsite/app/templates/category/beginner.html @@ -0,0 +1,18 @@ + + + + + + Beginner 그룹 목록 + + +

Beginner 그룹 목록

+ + + diff --git a/clubsite/app/templates/category/challenger.html b/clubsite/app/templates/category/challenger.html new file mode 100644 index 0000000..5b44f3e --- /dev/null +++ b/clubsite/app/templates/category/challenger.html @@ -0,0 +1,15 @@ + + + + + + Challenger Groups + + + +

Challenger 그룹 목록

+ + + + \ No newline at end of file diff --git a/clubsite/app/templates/errors/403.html b/clubsite/app/templates/errors/403.html new file mode 100644 index 0000000..b6392a2 --- /dev/null +++ b/clubsite/app/templates/errors/403.html @@ -0,0 +1,15 @@ + + + + + 403 Forbidden + + + +
+

403

+

접근이 금지된 페이지입니다.

+ 홈으로 돌아가기 +
+ + diff --git a/clubsite/app/templates/errors/404.html b/clubsite/app/templates/errors/404.html new file mode 100644 index 0000000..d415d0c --- /dev/null +++ b/clubsite/app/templates/errors/404.html @@ -0,0 +1,15 @@ + + + + + 404 Not Found + + + +
+

404

+

페이지를 찾을 수 없습니다.

+ 홈으로 돌아가기 +
+ + diff --git a/clubsite/app/templates/errors/500.html b/clubsite/app/templates/errors/500.html new file mode 100644 index 0000000..2ebc261 --- /dev/null +++ b/clubsite/app/templates/errors/500.html @@ -0,0 +1,15 @@ + + + + + 500 Internal Server Error + + + +
+

500

+

서버에 문제가 발생했습니다. 나중에 다시 시도해주세요.

+ 홈으로 돌아가기 +
+ + diff --git a/clubsite/app/templates/index.html b/clubsite/app/templates/index.html new file mode 100644 index 0000000..880083f --- /dev/null +++ b/clubsite/app/templates/index.html @@ -0,0 +1,52 @@ + + + + + + E-COPS + + + + + + +
+

E-COPS

+ +
+
+
+ BEGINNER +
+ +
+ + + + \ No newline at end of file diff --git a/clubsite/app/templates/index0.html b/clubsite/app/templates/index0.html new file mode 100644 index 0000000..c6c0e19 --- /dev/null +++ b/clubsite/app/templates/index0.html @@ -0,0 +1,16 @@ + + + + + + E-COPS + + + + + +

E-COPS

+ + + + \ No newline at end of file diff --git a/clubsite/app/templates/team/group.html b/clubsite/app/templates/team/group.html new file mode 100644 index 0000000..02e31ac --- /dev/null +++ b/clubsite/app/templates/team/group.html @@ -0,0 +1,14 @@ + + + + + + {{ group.name }} - 그룹 멤버 + + + +

{{ group.name }} - 멤버 목록

+ + + + \ No newline at end of file diff --git a/clubsite/app/templates/team/member.html b/clubsite/app/templates/team/member.html new file mode 100644 index 0000000..2d06ced --- /dev/null +++ b/clubsite/app/templates/team/member.html @@ -0,0 +1,16 @@ + + + + + + {{ member.name }} + + + +

{{ member.name }}

+

- 학과: {{ member.department }}

+

- 학번: {{ member['student number'] }}

+

- 블로그: {{ member.blog_url }}

+ + + \ No newline at end of file diff --git a/clubsite/app/templates/unauthorized.html b/clubsite/app/templates/unauthorized.html new file mode 100644 index 0000000..3015be8 --- /dev/null +++ b/clubsite/app/templates/unauthorized.html @@ -0,0 +1,36 @@ + + + + + + + + + 권한 대기 중 + + + + +
+ 현재 귀하의 계정은 관리자 승인을 기다리고 있습니다.
+ 로그인은 승인 후 가능하며, Seed값이 필요합니다. +
+ + + diff --git a/clubsite/config.py b/clubsite/config.py new file mode 100644 index 0000000..cec33ae --- /dev/null +++ b/clubsite/config.py @@ -0,0 +1,6 @@ +import os + +class Config: + SECRET_KEY = os.getenv('SECRET_KEY', 'devkey') + SQLALCHEMY_DATABASE_URI = 'sqlite:///club.db' + SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file diff --git a/clubsite/flaskenv b/clubsite/flaskenv new file mode 100644 index 0000000..7828951 --- /dev/null +++ b/clubsite/flaskenv @@ -0,0 +1,3 @@ +FLASK_APP=app +FLASK_ENV=development +FLASK_RUN_PORT=5000 \ No newline at end of file diff --git a/clubsite/instance/club.db b/clubsite/instance/club.db new file mode 100644 index 0000000000000000000000000000000000000000..d1cc458f1695e6d13d53a8b051fdab355c8febde GIT binary patch literal 28672 zcmeI)OK;OK00;179W5=4!eNRyOqhqDk=W#9ZPt#~t9mZ;&`J{#NWHwiC-gyOA74y?UkS_84h&yq4V~ zlhR>HmZcd&BuR?$7~?Sr!~8(RAjrYN5B5=M;^xDVqW)#8u#mTw$R%@$q+G|p$Az+G7UoMLFBRNR zC6;-?w9L|+d6k^-b5DHpg6%d!^+ldvIiYUU_Qe!Mk?)`M?fe`0Ni-z4)50Ip)$JZz z?s)6FdK;pxdyLm`s(gudo{v|huwed*#;)6jIvC_{ck<3~Oc@)KHwN<~C=B-dLOl&~ z|9F;sIf4oG9+6|p=%~CoFc$hE4u#qsjL9$Z@>v(+TeZ90TOH4-uGrm`ojIs|SGRsmI_xEO_CD7FMmo08Gh?( zLOm0SD(6OJ$!$CAc6Y7Gm+V!$-|P72Rbf-rMLK+D=%*DmQ8B_FH2cNFW`svMd0pZg z0t6rc0SG_<0uX=z1Rwwb2tWV=zbCM9T8>M~#!_58p7-A=tk!Ng4Sl-1V(S@W+Mptw zsx;DGDL=dJ+Vkc1&9&~Std^oqo;-c~`0hcOpKZO^eDnP2*5(7T{^x)ChX4TxKmY;|fB*y_ r009U<00Izzz#bOB`hO1(F1iK*2tWV=5P$##AOHafKmY;|*cSK%N*;Xd literal 0 HcmV?d00001 diff --git a/clubsite/requirements.txt b/clubsite/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c2055aa210063463cadf3ef61c7e845e823c5350 GIT binary patch literal 304 zcmZvXJqyAx6h+Tm@TXKv{lLYcOD7#fp>t8GqHU#CL4Uk@Uxr#mLI@-$=ic{vCY;E) zaAc+x(P!v)z~?1oPDRdv(o+KwlRwmorN~&u4SRMJ+&ynb=XXG=`&wxaomV-#sZ+jF zQ`Tx>g#ET|PQ+Q(M*d1|Lbf^^xk_#=Y5k1sPOwn>T*;ZI%{dZf