SIDE PROJECT
PromSpoon
좋은 프롬프트를 한 스푼
AI 프롬프트를 찾고, 저장하고, 바로 활용하는 Flutter 앱과 React 웹 서비스

PromSpoon - Apps on Google Play
AI 프롬프트를 검색·저장해 바로 활용하는 앱 · Google Play 배포 완료
https://play.google.com/store/apps/details?id=com.Dopaminus.promspoon&hl=ko
MOBILE APPLICATION
PromSpoon 앱
Flutter로 개발한 프롬프트 탐색·저장 앱입니다. 서버에서 내려온 콘텐츠를 검색·카테고리·정렬로 탐색하고, 상세 화면에서 본문을 복사하거나 ChatGPT·Gemini로 넘겨 바로 사용할 수 있도록 사용 흐름을 구성했습니다. 좋아요는 POST 요청으로 서버와 동기화하고, 북마크와 언어 설정은 기기에 남겨 앱을 다시 열어도 이어지게 했습니다.
- 앱 개발
- Flutter · Dart
- 상태 · 모델
- Riverpod · Freezed · JSON Serializable
- 네트워크
- Dart package:http · HTTPS JSON REST API · 15초 timeout
- 데이터 · 이미지
- PHP API · MariaDB · Cloudinary thumbnail URL
- 로컬 · 다국어
- SQLite(sqflite) · SharedPreferences · URL Launcher · 한국어 / 영어 / 일본어
담당 범위|Flutter 화면·상태·모델 설계, HTTP API 연동, 좋아요 POST 동기화, SQLite 북마크, Cloudinary 이미지 표시, 다국어와 예외 처리를 앱 전반에서 담당
APP SCREENS
앱 화면과 주요 기능
검색·카테고리 필터와 최신순·좋아요순 정렬로 프롬프트를 탐색합니다. 상세 화면에서는 본문을 복사하거나 ChatGPT·Gemini로 이동할 수 있고, 자주 쓰는 프롬프트는 북마크로 보관할 수 있습니다.
APP / 01
홈 · 검색 · 카테고리
Cloudinary 썸네일과 API에서 내려온 프롬프트를 홈 화면에 구성하고, 검색어와 카테고리로 필요한 콘텐츠를 빠르게 좁힐 수 있도록 했습니다.
- 검색어 기반 프롬프트 필터링
- 카테고리 선택과 전체 목록 전환
- 최신순·좋아요순 정렬 진입

APP / 02
목록 · 정렬 · 좋아요
목록에서 콘텐츠의 제목·요약·반응 수를 한눈에 보여주고, 좋아요와 북마크를 반복해서 사용해도 상태가 자연스럽게 이어지도록 구현했습니다.
- 최신순·좋아요순 목록 정렬
- 좋아요 POST와 낙관적 카운트 반영
- SQLite 북마크 상태 표시

APP / 03
프롬프트 상세 · 본문 복사
상세 화면에서 대표 이미지, 카테고리, 설명, 좋아요 수와 본문을 분리해 읽기 흐름을 만들고, 필요한 프롬프트를 바로 복사할 수 있게 했습니다.
- 프롬프트 메타데이터와 본문 구분
- 복사 동작과 저장 상태 피드백
- 게시일·반응 정보 표시

APP / 04
ChatGPT · Gemini · 관련 콘텐츠
프롬프트를 복사하는 데서 끝나지 않고 URL Launcher로 외부 AI 서비스로 이동하며, 같은 카테고리의 관련 프롬프트를 이어서 탐색할 수 있도록 했습니다.
- ChatGPT·Gemini 바로가기
- 외부 앱·브라우저 이동
- 좋아요 기준 관련 프롬프트 추천

APP / 05
다국어 · 설정 · 정책
앱 언어와 버전, 이용약관·개인정보처리방침을 한 화면에서 제공하고, 선택한 언어를 SharedPreferences에 저장해 재실행 후에도 유지했습니다.
- 한국어·영어·일본어 전환
- 앱 버전과 운영 정책 안내
- 설정값 로컬 저장 및 복원

Riverpod · Freezed
DataSource → Repository → Provider → UI로 책임을 나누고, Riverpod으로 비동기 데이터를 관리했습니다. Freezed와 JSON Serializable로 불변 모델과 JSON 변환 코드를 구성했습니다.
기기에 남는 북마크 · 다국어
SQLite에 프롬프트와 저장 시점을 보관해 북마크를 관리합니다. SharedPreferences에는 언어 설정을 저장하고 한국어·영어·일본어 UI를 제공합니다.
APP IMPLEMENTATION
Flutter 앱 구현 범위
화면을 그리는 데서 끝내지 않고, API 응답을 모델로 변환하는 시점부터 사용자가 다시 앱을 열었을 때의 북마크·언어 상태까지 전체 흐름을 구현했습니다. 원격 콘텐츠와 기기 상태를 분리하고 기능별 폴더의 책임을 나눠 API나 저장 방식이 바뀌어도 화면 로직이 흔들리지 않도록 구성했습니다.
검색·카테고리·정렬
Riverpod의 SearchQueryNotifier와 SelectedCategoryNotifier로 검색어·카테고리를 분리해 관리하고, 최신순·좋아요순을 PromptSortNotifier로 전환했습니다. 목록은 같은 API 응답을 바탕으로 화면에서 즉시 필터링해 탐색 흐름을 빠르게 유지했습니다.
HTTP 네트워크 계층
Dio 대신 Dart의 package:http Client를 주입해 HomeRemoteDataSource를 만들었습니다. GET /api/prompts와 /api/tables/prompt_categories를 호출하고, 15초 timeout·상태 코드 확인·UTF-8 bodyBytes 디코딩·JSON 파싱을 한 곳에서 처리했습니다.
좋아요 POST와 낙관적 UI
POST /api/prompts/{slug}/like에 liked 값을 JSON으로 보내고 X-PromSpoon-Visitor 헤더로 방문자를 구분했습니다. 화면에서는 먼저 좋아요·카운트를 반영한 뒤 서버 결과를 확정하고, 연속 탭은 syncing key로 묶어 중복 요청을 줄였습니다.
Cloudinary 이미지 서버
API의 thumbnail_image_url로 내려오는 Cloudinary 이미지를 프롬프트 목록·상세 카드에 표시했습니다. 이미지 주소를 콘텐츠 데이터와 함께 관리해 새 이미지를 앱 번들에 다시 포함하지 않고 운영 중 교체할 수 있도록 했습니다.
SQLite 북마크·설정 보관
favorite_prompts 테이블에 프롬프트 JSON과 saved_at을 저장하고 insert·replace·delete를 조건 바인딩으로 처리했습니다. SharedPreferences에는 언어 선택을 저장해 한국어·영어·일본어 설정을 재실행 후에도 유지했습니다.
Freezed 모델과 기능 구조
Prompt·PromptCategory·PromptLikeResult·PromptLikeStatus를 Freezed 모델로 정의하고 JSON Serializable 생성 코드를 사용했습니다. DataSource → Repository → Riverpod Provider/Notifier → UI 구조로 API·로컬 데이터·화면의 결합도를 낮췄습니다.
API & DATA FLOW
앱에서 API와 DB까지 이어지는 데이터 흐름
앱은 package:http로 PHP·MariaDB REST API를 호출합니다. API는 프롬프트 본문·카테고리·태그·좋아요 수·공개 상태·다국어 필드를 JSON으로 내려주고, 썸네일은 Cloudinary URL을 함께 전달합니다. 좋아요는 같은 API에 POST하고 응답의 카운트로 서버 상태를 확정합니다.
- 01
Flutter UI
검색·카테고리·좋아요 입력을 Riverpod 상태로 수집
- 02
http Client
GET 목록·카테고리와 POST 좋아요 요청을 HTTPS로 전송
- 03
PHP API · MariaDB
프롬프트·카테고리·반응 수와 공개 상태를 DB에서 조회·갱신
- 04
JSON · Cloudinary
Freezed 모델로 변환하고 thumbnail_image_url을 화면에 표시

thumbnail_image_url을 한 응답으로 받아 Freezed 모델에 매핑했습니다.목록 GET 응답
prompts 테이블의 prompt_id·slug·title·summary·tags·like_count·published_at·is_published와 한국어·영어·일본어 필드를 함께 받아 언어 설정에 맞게 표시합니다.
반응 POST 응답
liked 값을 전송한 뒤 서버가 계산한 liked·like_count를 받아 로컬의 임시 상태와 교체합니다. 요청 실패 시 마지막 확정 상태로 되돌려 화면과 서버가 어긋나지 않게 했습니다.
PROJECT STRUCTURE
기능 단위로 나눈 앱 구조
홈 · 원격 콘텐츠
lib/features/home/data_source/home_remote_data_source.dart
lib/features/home/repository/home_repository.dart
lib/features/home/provider/home_provider.dart
lib/features/home/model/home_model.dart기기 데이터 · 설정
lib/features/favorite/favorite_local_data_source.dart
lib/features/favorite/favorite_repository.dart
lib/core/locale/app_language.dart
lib/features/prompt_detail/prompt_detail_page.dart구현 관점 — API 응답·로컬 저장·상태 변경을 화면 코드에 섞지 않고 기능 단위로 분리했습니다. 그래서 좋아요 동기화나 Cloudinary 주소 규칙이 바뀌어도 UI 변경 범위를 제한할 수 있습니다.
사용자 관점 — 검색→상세→복사·외부 AI 실행→북마크, 그리고 좋아요→목록 갱신까지 실제 사용 흐름을 기준으로 연결했습니다.
WEB APPLICATION
PromSpoon 웹
React·Next.js로 구현한 프롬프트 웹 서비스입니다. 검색과 콘텐츠 열람을 제공하고, MySQL 기반 API, 관리자 페이지, 인증·보안 및 Cronjob 예약 공개 기능을 함께 개발했습니다.
- 웹 개발
- React · Next.js · TypeScript · Tailwind CSS
- API · 데이터
- Next.js Route Handlers · REST API · MySQL(mysql2)
- 배치 · 배포
- Shell · PHP · PDO · Cronjob(Crontab / Railway Cron) · Railway
- 인증 · 보호
- HMAC 서명 세션 · 보안 쿠키 · 동일 출처 확인 · 요청 제한
담당 역할|웹 화면·API 개발, DB 연동, 관리자 인증과 콘텐츠 관리, 보안 및 예약 공개 자동화
PromSpoon 웹사이트
PromSpoon 웹 메인 화면
WEB SCREENS
웹 화면과 주요 기능
React·Next.js로 검색, 카테고리, 프롬프트 목록·상세 화면을 구현했습니다. MySQL과 연동한 콘텐츠 API를 기반으로 탐색부터 본문 활용까지 연결하고, 예약 콘텐츠와 앱 전용 기능은 이용 가능한 시점과 경로를 명확하게 안내했습니다.
WEB / 01
콘텐츠 탐색 및 예약 안내
프롬프트 목록에서 콘텐츠를 살펴보고, 공개 예정인 테스트의 이용 가능 시점을 확인할 수 있도록 구성했습니다.
- 검색·카테고리 기반 콘텐츠 탐색
- 좋아요 및 프롬프트 복사
- 예약 공개 일자와 앱 전용 여부 표시

WEB / 02
프롬프트 상세 및 활용
콘텐츠 소개와 프롬프트 본문을 구분해 정보의 흐름을 정리하고, 필요한 내용을 바로 복사할 수 있도록 구현했습니다.
- 제목·카테고리·설명·반응 정보 제공
- 프롬프트 본문 열람 및 복사
- 목록에서 상세 화면으로 연결

WEB / 03
모바일 앱 이용 안내
앱 전용 테스트를 선택하면 이용 조건과 다운로드 경로를 안내하여 웹에서 모바일 앱으로 이어지는 흐름을 제공합니다.
- 앱 전용 콘텐츠 안내 모달
- Google Play 다운로드 경로 연결
- 나중에 보기 선택으로 웹 탐색 유지

SERVER ENVIRONMENTS
개발 서버와 라이브 서버 분리 운영
PromSpoon은 기능 개발과 검증을 위한 개발 서버, 실제 이용자를 위한 라이브 서버를 분리해 운영했습니다. 변경 사항은 개발 서버에서 API와 데이터베이스 연동을 확인한 뒤 라이브 서버에 반영하는 흐름으로 관리했습니다.
개발 서버 · 기능 검증
새 기능과 수정 사항을 개발 환경에서 먼저 구현하고, 앱·웹 API 및 데이터베이스 연동을 확인합니다.
라이브 서버 · 서비스 운영
개발 서버에서 검증한 변경 사항을 라이브 환경에 배포해 실제 사용자가 이용하는 앱·웹 서비스를 운영합니다.
DATABASE TABLES
PromSpoon 데이터베이스 테이블 구성

ADMIN & SECURITY
콘텐츠를 관리하고, 접근을 보호하고
관리자 화면에서 프롬프트 제목·요약·본문, 카테고리·태그와 다국어 콘텐츠를 등록하고 공개 여부를 관리합니다. 관리자 기능은 서버에서 세션과 요청 출처를 확인한 뒤 처리하도록 구성했습니다.

프롬프트 관리자 로그인
서명 세션 · 보안 쿠키
HMAC으로 서명한 세션의 유효기간과 서명을 서버에서 검증합니다. HttpOnly·SameSite=Strict 쿠키를 사용하고 운영 환경에서는 Secure 속성을 적용합니다.
동일 출처 확인 · 요청 제한
관리자 요청의 Origin을 확인하고, 관리자 로그인과 공개 API·좋아요 요청에는 DB 기반 요청 제한을 적용합니다.
입력 검증 · SQL 바인딩
슬러그·카테고리·본문 길이 등 입력값을 검증합니다. SQL은 파라미터 바인딩으로 처리하고, 공개 데이터 API는 허용된 리소스와 필드로 제한합니다.
서버 설정 · 오류 응답
인증 정보와 DB 연결 설정은 서버 환경변수로 관리합니다. 상세 오류는 서버 로그에 남기고 클라이언트에는 일반화한 오류 응답을 제공합니다.
SCHEDULING
Cronjob 기반 콘텐츠 자동 공개
Shell 실행 파일과 PHP 처리 파일을 분리한 뒤 Cronjob에 등록했습니다. 콘텐츠별로 설정한 기간(일수)이 경과해 공개 예정일에 도달하면, DB의 공개 상태를 자동으로 수정하고 앱·웹에 콘텐츠가 공개되도록 개선했습니다.
파일별 역할
실행을 담당하는 Shell과 DB 처리를 담당하는 PHP를 분리했습니다.
- SHELL · 실행
scripts/publish-mobile-prompts.sh - Cronjob에서 주기적으로 실행하는 파일입니다. PHP 처리 파일을 호출하고 실행 인자를 전달합니다.
- PHP · 데이터 처리
scripts/publish-mobile-prompts.php - PDO로 MySQL에 연결합니다. 설정한 기간이 경과한 미공개 콘텐츠를 조회하고 DB의 공개 상태를 변경합니다.
자동 공개 처리 흐름
- 01
Cronjob 실행
등록된 스케줄에 따라 Shell 파일을 주기적으로 실행
- 02
PHP 호출
Shell에서 예약 공개를 담당하는 PHP 파일 실행
- 03
기간 경과 확인
공개 예정 일시와 DB 현재 시간을 비교해 처리 대상 조회
- 04
DB 갱신 · 공개
공개 상태를 변경하고 앱·웹에 콘텐츠 자동 노출
공개 조건
published_at <= CURRENT_TIMESTAMPDB 상태 변경
is_published: 0 → 1비공개 → 공개
운영 안정성
Dry-run으로 변경 전 처리 대상을 확인할 수 있도록 했습니다. 실제 갱신은 트랜잭션과 행 잠금으로 처리하고, 실패 시 롤백 및 오류 로그를 남기도록 구성했습니다.
WEB DEVELOPMENT
웹 개발 및 운영 범위
콘텐츠를 제공하는 웹 화면과 API, 관리자 인증·요청 제한·다국어 데이터 관리까지 함께 설계했습니다.
Shell 실행과 PHP DB 처리를 분리하고 Cronjob으로 예약 공개를 자동화했습니다. 트랜잭션·행 잠금과 dry-run·로그·실패 롤백을 구성해 배치 실행을 관리할 수 있도록 했습니다.