Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Distop — 안전교육 배정·정산 관리 시스템

Next.js TypeScript React Native Expo Prisma Supabase PostgreSQL TailwindCSS

안전교육 강사(요원) 배정 업무를 엑셀·수기 처리에서 벗어나게 하기 위한 시스템입니다. 학교의 교육 의뢰 접수부터 강사 배정, 견적서 발행, 정산 수수료 산출, 배정 결과의 모바일 실시간 알림까지 하나의 어드민 웹과 하나의 모바일 앱으로 처리합니다.

🔗 Live Demo: distop-rho.vercel.app (관리자 로그인 필요 — 실제 화면은 아래 데모 GIF 참고)


🙋 My Role (담당 업무)

기획부터 어드민 웹, 모바일 앱, DB 설계까지 전 영역을 단독으로 개발했습니다.

  • 도메인 모델 설계: 의뢰(Job) → 배정(Assignment) → 정산(Settlement)으로 이어지는 핵심 플로우와 학교/교사/강사(요원)/견적/공지/푸시 발송이력을 아우르는 Prisma 스키마(20여 개 모델) 설계
  • 어드민 웹 기능 구현: 의뢰 접수·자동 견적 계산, 드래그앤드롭 기반 배정 현황 보드(@dnd-kit), 월별 일정 캘린더, PDF 견적서 생성(@react-pdf/renderer) 및 이메일 발송(Resend) 구현
  • 모바일 앱 설계·구현: Expo Router 기반으로 학교(교사)/요원 두 역할의 탭 네비게이션을 분리 설계하고, 로그인부터 배정 확인·정산 조회·공지 열람까지의 REST API 연동 구현
  • DB 보안 모델 설계: Supabase Security Advisor가 지적한 RLS 취약점을 SECURITY INVOKER 뷰, 컬럼 단위 SELECT 권한, SECURITY DEFINER 헬퍼 함수로 해결 (자세히)
  • 인증/계정 발급 흐름 설계: 전화번호·이메일 기반 가상 계정을 어드민에서 발급하고, 최초 로그인 시 임시 비밀번호 변경을 강제하는 흐름 구현

🛠️ Tech Stack

Area Stack
Admin Web Next.js 16 (App Router), TypeScript, TailwindCSS, Recharts, @dnd-kit
Mobile App Expo SDK 57 (Expo Router), React Native, TanStack Query
Backend / API Next.js API Routes, Supabase Auth (JWT/Bearer 인가)
Database Supabase (PostgreSQL), Prisma ORM, Row Level Security
기타 연동 Resend(이메일 발송), @react-pdf/renderer(견적서 PDF), Expo Notifications(푸시), EAS Update(OTA 배포)

📂 프로젝트 구조

Distop/
├── distop-admin/         # Next.js 기반 어드민 웹 애플리케이션 & API 백엔드
│   ├── prisma/           # Prisma 스키마 및 DB 마이그레이션 SQL 스크립트
│   └── src/app/          # Next.js App Router 구조 (대시보드, 일정, 의뢰, 정산 관리)
│
├── distop-app/           # React Native / Expo 기반 모바일 애플리케이션
│   ├── app/               # Expo Router 구조 (요원 전용/학교 교사 전용 탭 레이아웃)
│   └── lib/                # Supabase 클라이언트 및 세션 저장소 공통 모듈
│
└── .agents/               # 개발 에이전트를 위한 프로젝트 컨벤션 및 RLS 검증 규칙 (AGENTS.md)

🏛️ 시스템 아키텍처

graph TD
    %% Clients
    Admin[어드민 웹 Panel <br/> Next.js / Tailwind]
    App[모바일 APP <br/> Expo / React Native]

    %% APIs
    AdminAPI[Next.js API Routes <br/> /api/*]
    SupaSDK[Supabase Client SDK <br/> authenticated / anon]

    %% Database
    subgraph Supabase / PostgreSQL
        PRISMA_BYPASS[Prisma Client <br/> postgres bypass RLS]
        
        subgraph Security Invoker Views
            ViewA[school_assignments_view]
            ViewB[staff_settlements_view]
        end

        subgraph Raw Tables & RLS Policies
            TableA[(jobs)]
            TableB[(assignments)]
            TableC[(settlements)]
            TableD[(schools)]
            TableE[(notices / push logs)]
        end
        
        Helper[check_job_school <br/> SECURITY DEFINER 헬퍼 함수]
    end

    %% Connections
    Admin -->|Prisma| AdminAPI
    AdminAPI -->|postgres role| PRISMA_BYPASS
    PRISMA_BYPASS --> TableA
    PRISMA_BYPASS --> TableB
    PRISMA_BYPASS --> TableC

    App -->|Next.js REST API| AdminAPI
    App -->|Supabase SDK| SupaSDK
    
    %% RLS & Views evaluation
    SupaSDK -->|SELECT| ViewA
    SupaSDK -->|SELECT| ViewB
    SupaSDK -->|Direct Blocked| TableC
    
    ViewB -->|Reads minimum columns| TableC
    ViewA -->|check_job_school| Helper
    Helper -->|Read bypass recursion| TableA
    TableB -->|RLS checking| Helper
Loading

🔍 Key Challenges / Troubleshooting

1) Supabase RLS 상호 순환 참조(infinite recursion) 및 Security Advisor 오류 해결

jobs 테이블의 RLS 조건이 assignments를 검사하고, 동시에 assignments의 RLS 조건이 jobs를 다시 조회하면서 무한 재귀(infinite recursion detected)가 발생했습니다. 원인을 추적한 뒤 SECURITY DEFINER 권한의 내부 헬퍼 함수(check_job_school())로 조회 단계를 끊어 순환을 해소했습니다. 이어서 매출액·계좌번호 등 민감 컬럼이 원본 테이블 SELECT 권한을 통해 그대로 노출되던 Security Advisor 경고를 해결하기 위해, 원본 테이블은 필수 식별 컬럼(id, jobId 등)에만 SELECT 권한을 남기고 나머지 조회는 호출자 JWT 권한으로 실행되는 SECURITY INVOKER 뷰(school_assignments_view, staff_settlements_view)로 격리했습니다.

2) 모바일 앱 크로스플랫폼 세션 스토리지 분기 & SSR 빌드 실패 예방

Expo 앱을 웹으로도 빌드해야 하는데, 네이티브 전용 모듈인 expo-secure-store를 그대로 사용하면 SSR/웹 빌드 시 컴파일이 깨지는 문제가 있었습니다. Platform.OS를 기준으로 네이티브에서는 SecureStore, 브라우저에서는 localStorage, Node.js SSR 환경에서는 인메모리 MemoryStorage로 자동 분기되는 스토리지 어댑터를 만들어 세 환경 모두에서 빌드가 깨지지 않도록 했습니다.

3) API 입력값 정합성 처리 (빈 문자열 → NULL 변환)

폼에서 선택하지 않은 외래키 필드(schoolId, teacherId 등)가 빈 문자열("")로 전송되면 DB의 ENUM/외래키 제약조건 에러가 발생했습니다. API 레이어에서 빈 문자열을 null로 정규화하는 처리를 의뢰·강사·교사 등록/수정 API 전반에 일관되게 적용해 재발을 막았습니다.


🖥️ 어드민 웹 (distop-admin)

  • 의뢰 관리 (/admin/jobs): 접수 대기, 견적서 발행 및 자동 금액 계산
  • 배정 현황 (/admin/assignments/status): 실시간 활성 강사 배정 현황 모니터링 및 상태 추적
  • 일정 캘린더 (/admin/calendar): 월별 강습 및 배정 일정 통합 뷰어
  • 알림 발송 이력 (/admin/push-logs): 요원 및 교사 대상 모바일 푸시 발송 결과 및 읽음 트래킹

📱 모바일 앱 (distop-app)

  • 역할 기반 라우팅: 요원/학교(교사) 계정을 자동 분기해 서로 다른 탭 레이아웃과 화면 제공
  • 배정·정산·공지 조회: 배정된 일정 확인, 정산 내역 조회, 학교용 견적서 PDF 열람, 공지사항 확인
  • Safe Area 반응형 적용: 기기별 노치·하단 홈 제스처 바에 하단 탭 버튼이 가려지지 않도록 useSafeAreaInsets() 기반 동적 패딩 적용
  • OTA 업데이트: EAS Update로 스토어 심사 없이 프리뷰/프로덕션 채널에 앱 업데이트 배포

🚀 Getting Started

어드민 로컬 개발 및 빌드

cd distop-admin

# 의존성 설치
npm install

# .env.example을 참고해 .env.local 작성 (Supabase URL/키, DATABASE_URL 등)
cp .env.example .env.local

# Prisma 클라이언트 자동 생성 및 데이터베이스 마이그레이션 적용
npx prisma generate
npx prisma db push

# 로컬 개발 서버 실행
npm run dev

# 프로덕션 빌드 검증
npm run build

모바일 앱 로컬 개발

cd distop-app

# 의존성 설치
npm install

# .env.example을 참고해 .env 작성 (Supabase URL/키, API Base URL)
cp .env.example .env

# 개발 서버 실행
npm run start

모바일 앱 OTA 배포 (EAS Update)

보안 망 프록시 내부에서 발생할 수 있는 Node.js SSL 인증서 오류 우회 명령어와 함께 프리뷰 브랜치에 배포합니다.

cd distop-app

# Node.js SSL 인증 검사 임시 해제 후 안드로이드 및 iOS 번들 업데이트
$env:NODE_TLS_REJECT_UNAUTHORIZED="0"
eas update --branch preview --platform android --environment preview --message "업데이트 내용 기술"
eas update --branch preview --platform ios --environment preview --message "업데이트 내용 기술"

📸 Screenshots / Demo

어드민 웹

어드민 웹 데모

어드민 대시보드

연간 총매출·요원비·순수익, 월별 매출 추이, 의뢰 상태 현황을 한눈에 보는 대시보드입니다.

의뢰 관리 목록

등록된 의뢰 목록을 접수대기~근무완료 상태별로 필터링해 확인합니다.

의뢰 등록 폼

의뢰 등록 폼입니다. 저장 시 견적서가 자동 생성됩니다.

배정 현황

의뢰별 배정 인원 현황과 배정된 요원 목록을 조율·관리하는 화면입니다.

일정 캘린더

월별 캘린더 뷰로 교육 일정과 배정 현황을 파악합니다.

알림 발송 이력

요원/교사 대상 푸시 알림 발송 결과와 읽음 현황을 확인합니다.

모바일 앱

모바일 앱 데모

내 프로필

요원의 내 프로필 화면입니다. 인적사항과 지급 계좌 정보를 확인합니다.

배정 일정 리스트 배정 일정 캘린더

배정된 일정을 리스트/캘린더 두 가지 뷰로 확인할 수 있습니다.

정산 내역

근무 완료 건에 대한 정산 내역을 조회합니다.


🔧 Development Process

  • 커밋 컨벤션: feat, fix, docs, chore 등 Conventional Commits 스타일을 사용하고, 변경 영역이 명확할 때는 fix(ui):, feat(mobile):, feat(auth,database):처럼 스코프를 함께 표기했습니다.
  • 개발 방식: 1인 개발로 별도 브랜치 전략 없이 main 브랜치에 직접 커밋하는 방식으로 진행했습니다.

📬 Contact

About

학교 안전교육 강사 배정·정산 관리 시스템 — Next.js 어드민 웹 + Expo 모바일 앱, Supabase/Prisma 기반

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages