## 적용된 변경사항
### 백엔드
- **`wos_coupons` 테이블에 `is_expired` 컬럼 추가** (TypeORM synchronize로 자동 생성)
- **`redeemAll` 로직 개선**:
- 이미 `성공` / `이미 수령` 로그 있는 쿠폰 → 자동 **스킵** (재지급 안 함)
- `err_code: 40008` (RECEIVED) → **성공** 처리 (`이미 수령` 메시지)
- `err_code: 40007` (TIME ERROR) → DB에서 `is_expired = true, is_active = false` 업데이트
- **매일 오전 9시 자동 실행** (`@Cron('0 0 9 * * *')`) - 저장된 모든 유저에게 미수령 쿠폰 자동 지급
### 프론트엔드
- **삭제 버튼 제거**
- 만료된 쿠폰은 **보라색 "만료" 배지** 표시, 토글 버튼 숨김
- 활성 쿠폰 카운트에서 만료 제외
120 lines
2.8 KiB
TypeScript
120 lines
2.8 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import axios from 'axios';
|
|
|
|
interface PlayerInfo {
|
|
nickname: string;
|
|
avatar_image: string;
|
|
stove_lv: number;
|
|
}
|
|
|
|
interface RedeemResult {
|
|
name: string;
|
|
code: string;
|
|
status: 'success' | 'error' | 'pending';
|
|
message: string;
|
|
}
|
|
|
|
interface HistoryItem {
|
|
id: number;
|
|
fid: string;
|
|
coupon_code: string;
|
|
result_msg: string;
|
|
executed_at: string;
|
|
}
|
|
|
|
export interface Coupon {
|
|
id: number;
|
|
name: string;
|
|
code: string;
|
|
is_active: boolean;
|
|
is_expired: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface SavedUser {
|
|
fid: string;
|
|
nickname: string;
|
|
avatar_url: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export const useWosStore = defineStore('wos', {
|
|
state: () => ({
|
|
fid: '',
|
|
player: null as PlayerInfo | null,
|
|
results: [] as RedeemResult[],
|
|
history: [] as HistoryItem[],
|
|
coupons: [] as Coupon[],
|
|
savedUsers: [] as SavedUser[],
|
|
status: 'idle' as 'idle' | 'loading' | 'success' | 'error',
|
|
errorMsg: '',
|
|
}),
|
|
|
|
actions: {
|
|
async searchPlayer(fid: string) {
|
|
this.status = 'loading';
|
|
this.errorMsg = '';
|
|
this.player = null;
|
|
this.results = [];
|
|
try {
|
|
const { data } = await axios.post('/api/player', { fid });
|
|
this.player = data;
|
|
this.fid = fid;
|
|
await Promise.all([this.loadHistory(), this.redeemAll()]);
|
|
this.status = 'idle';
|
|
} catch (err: any) {
|
|
this.status = 'error';
|
|
this.errorMsg = err.response?.data?.message || '유저를 찾을 수 없습니다.';
|
|
}
|
|
},
|
|
|
|
async redeemAll() {
|
|
if (!this.fid) return;
|
|
try {
|
|
const { data } = await axios.post('/api/redeem-all', { fid: this.fid });
|
|
this.results = data;
|
|
await this.loadHistory();
|
|
} catch (err: any) {
|
|
this.errorMsg = err.response?.data?.message || '쿠폰 지급 중 오류 발생';
|
|
}
|
|
},
|
|
|
|
async loadHistory() {
|
|
if (!this.fid) return;
|
|
try {
|
|
const { data } = await axios.get(`/api/history/${this.fid}`);
|
|
this.history = data;
|
|
} catch {}
|
|
},
|
|
|
|
async loadSavedUsers() {
|
|
try {
|
|
const { data } = await axios.get('/api/users');
|
|
this.savedUsers = data;
|
|
} catch {}
|
|
},
|
|
|
|
async loadCoupons() {
|
|
try {
|
|
const { data } = await axios.get('/api/coupons');
|
|
this.coupons = data;
|
|
} catch {}
|
|
},
|
|
|
|
async addCoupon(name: string, code: string) {
|
|
await axios.post('/api/coupons', { name, code });
|
|
await this.loadCoupons();
|
|
},
|
|
|
|
async toggleCoupon(id: number, is_active: boolean) {
|
|
await axios.patch(`/api/coupons/${id}`, { is_active });
|
|
await this.loadCoupons();
|
|
},
|
|
|
|
async deleteCoupon(id: number) {
|
|
await axios.delete(`/api/coupons/${id}`);
|
|
await this.loadCoupons();
|
|
},
|
|
},
|
|
});
|