Skip to content

Commit fe68f71

Browse files
feat(v0.3): 역할 세분화 + 공지시스템 + 학생대시보드 + 개발자센터
Backend - User 모델: professor_role/admin_role/nationality/grade 추가 - ProfessorRole enum (전임/겸임/학과장) - AdminRole enum (교무처/학생처/사무국/기획처/IT센터/입학홍보처/총괄) - Announcement 모델 (target_audience/type/send_email) - GET/POST/DELETE /announcements + 이메일 동시 발송 - PATCH /users/{id}/role (역할+세부역할 변경) - GET /dev/health-check + /dev/stats + /dev/settings - Alembic 0012 Frontend - AnnouncementBar: 상단 배너(URGENT) + Footer 슬라이드 알림 - 학생 대시보드: 과제카드 + 공지 + 백분위 + 교수/개발자 전용 링크 - /dev 개발자 센터: 4탭 (시스템현황/사용자CRUD/설정/LLM-RAG)
1 parent 137d775 commit fe68f71

11 files changed

Lines changed: 1017 additions & 20 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""role refinement + announcements table
2+
3+
Revision ID: 0012_roles_announcements
4+
Revises: 0011_assignments
5+
Create Date: 2026-04-20 09:00:00
6+
"""
7+
8+
from collections.abc import Sequence
9+
10+
import sqlalchemy as sa
11+
from alembic import op
12+
from sqlalchemy.dialects import postgresql
13+
14+
revision: str = "0012_roles_announcements"
15+
down_revision: str | None = "0011_assignments"
16+
branch_labels: str | Sequence[str] | None = None
17+
depends_on: str | Sequence[str] | None = None
18+
19+
20+
def upgrade() -> None:
21+
# --- Enums ---
22+
for name, values in [
23+
("professor_role_enum", ["FULL_TIME", "ADJUNCT", "DEPT_HEAD"]),
24+
("admin_role_enum", ["ACADEMIC_AFFAIRS", "STUDENT_AFFAIRS", "GENERAL_ADMIN", "PLANNING", "IT_CENTER", "ADMISSIONS", "SUPER_ADMIN"]),
25+
("nationality_enum", ["KOREAN", "INTERNATIONAL"]),
26+
("announcement_target_enum", ["ALL", "STUDENT", "PROFESSOR", "ADMIN", "DEVELOPER"]),
27+
("announcement_type_enum", ["GENERAL", "MAINTENANCE", "URGENT"]),
28+
]:
29+
postgresql.ENUM(*values, name=name, create_type=True).create(op.get_bind(), checkfirst=True)
30+
31+
# --- User model extensions ---
32+
op.add_column("users", sa.Column("professor_role", postgresql.ENUM("FULL_TIME", "ADJUNCT", "DEPT_HEAD", name="professor_role_enum", create_type=False), nullable=True))
33+
op.add_column("users", sa.Column("admin_role", postgresql.ENUM("ACADEMIC_AFFAIRS", "STUDENT_AFFAIRS", "GENERAL_ADMIN", "PLANNING", "IT_CENTER", "ADMISSIONS", "SUPER_ADMIN", name="admin_role_enum", create_type=False), nullable=True))
34+
op.add_column("users", sa.Column("nationality", postgresql.ENUM("KOREAN", "INTERNATIONAL", name="nationality_enum", create_type=False), nullable=True))
35+
op.add_column("users", sa.Column("grade", sa.Integer(), nullable=True))
36+
37+
# --- Announcements ---
38+
op.create_table(
39+
"announcements",
40+
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
41+
sa.Column("author_id", postgresql.UUID(as_uuid=True), nullable=True),
42+
sa.Column("title", sa.String(200), nullable=False),
43+
sa.Column("content", sa.Text(), nullable=False),
44+
sa.Column("target_audience", postgresql.ENUM("ALL", "STUDENT", "PROFESSOR", "ADMIN", "DEVELOPER", name="announcement_target_enum", create_type=False), nullable=False),
45+
sa.Column("announcement_type", postgresql.ENUM("GENERAL", "MAINTENANCE", "URGENT", name="announcement_type_enum", create_type=False), nullable=False),
46+
sa.Column("is_active", sa.Boolean(), server_default="true", nullable=False),
47+
sa.Column("starts_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
48+
sa.Column("ends_at", sa.DateTime(timezone=True), nullable=True),
49+
sa.Column("send_email", sa.Boolean(), server_default="false", nullable=False),
50+
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
51+
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
52+
sa.PrimaryKeyConstraint("id"),
53+
sa.ForeignKeyConstraint(["author_id"], ["users.id"], ondelete="SET NULL"),
54+
)
55+
op.create_index("ix_announcements_active_target", "announcements", ["is_active", "target_audience"])
56+
op.create_index("ix_announcements_type", "announcements", ["announcement_type"])
57+
58+
59+
def downgrade() -> None:
60+
op.drop_table("announcements")
61+
op.drop_column("users", "grade")
62+
op.drop_column("users", "nationality")
63+
op.drop_column("users", "admin_role")
64+
op.drop_column("users", "professor_role")
65+
for name in ["announcement_type_enum", "announcement_target_enum", "nationality_enum", "admin_role_enum", "professor_role_enum"]:
66+
postgresql.ENUM(name=name).drop(op.get_bind(), checkfirst=True)

0 commit comments

Comments
 (0)