Fix for production

This commit is contained in:
mahmood
2026-09-10 22:37:29 +03:30
parent efb0051ab9
commit ec899defb9
95 changed files with 219589 additions and 110 deletions
+11 -4
View File
@@ -1,11 +1,10 @@
node_modules
**/node_modules
dist
**/dist
.git
.idea
.vscode
.env
.env.local
*.log
npm-debug.log*
yarn-debug.log*
@@ -13,5 +12,13 @@ yarn-error.log*
pnpm-debug.log*
.npm
.pnpm-store
Dockerfile*
docker-compose*.yml
coverage
uploads
docs
iranSans
apps/api/src
apps/api/dist
apps/api/test
apps/admin/src
apps/client/src
packages
+36
View File
@@ -0,0 +1,36 @@
# کپی به env سرویس API در Dokploy (یا فایل .env کنار compose)
# دامنه‌ها:
# wheel.montzar.ir → سرویس client
# api.wheel.montzar.ir → سرویس api
# adm.wheel.montzar.ir → سرویس admin
POSTGRES_DB=lucky_wheel
POSTGRES_USER=postgres
POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD
# پورت خارجی برای اتصال از بیرون (مثلاً DBeaver) — داخل شبکه همچنان 5432 است
EXPORT_DB_PORT=5432
# داخل شبکه compose هاست postgres است (نه localhost)
DATABASE_URL=postgresql://postgres:CHANGE_ME_STRONG_PASSWORD@postgres:5432/lucky_wheel?schema=public
REDIS_PASSWORD=
JWT_USER_SECRET=CHANGE_ME_USER_JWT
JWT_USER_EXPIRES_IN=7d
JWT_ADMIN_ACCESS_SECRET=CHANGE_ME_ADMIN_ACCESS_JWT
JWT_ADMIN_ACCESS_EXPIRES_IN=1h
JWT_ADMIN_REFRESH_SECRET=CHANGE_ME_ADMIN_REFRESH_JWT
JWT_ADMIN_REFRESH_EXPIRES_IN=7d
CORS_ADMIN_ORIGIN=https://adm.wheel.montzar.ir
CORS_CLIENT_ORIGIN=https://wheel.montzar.ir
STORAGE_DRIVER=local
STORAGE_LOCAL_ROOT=./uploads
STORAGE_PUBLIC_URL=https://api.wheel.montzar.ir/uploads
SMS_DRIVER=mock
RAYGAN_SMS_USERNAME=
RAYGAN_SMS_PASSWORD=
RAYGAN_SMS_LINE_NUMBER=
RAYGAN_SMS_API_BASE_URL=https://api.raygansms.com
+9 -1
View File
@@ -1,5 +1,8 @@
node_modules/
dist/
# بیلد میانی Nest — فقط release/ روی گیت می‌رود
apps/api/dist/
apps/api/release/node_modules/
packages/shared-types/dist/
build/
.env
.env.local
@@ -8,3 +11,8 @@ uploads/
coverage/
.DS_Store
Thumbs.db
# فرانت و API release برای Dokploy (کپی‌only) باید commit شوند:
# apps/admin/dist/**
# apps/client/dist/**
# apps/api/release/** (بدون node_modules)
+1
View File
@@ -0,0 +1 @@
VITE_API_BASE_URL=https://api.wheel.montzar.ir/api
+5 -25
View File
@@ -1,30 +1,10 @@
FROM node:20-alpine AS builder
FROM docker.arvancloud.ir/nginx:latest
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable pnpm
# بیلد لوکال: pnpm --filter @lucky-wheel/admin build
# (با apps/admin/.env.production)
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @lucky-wheel/admin build
FROM nginx:alpine
COPY --from=builder /app/apps/admin/dist /usr/share/nginx/html
# Configure Nginx for Single Page Application
RUN echo 'server { \
listen 80; \
server_name localhost; \
location / { \
root /usr/share/nginx/html; \
index index.html index.htm; \
try_files $uri $uri/ /index.html; \
} \
}' > /etc/nginx/conf.d/default.conf
COPY apps/admin/nginx.conf /etc/nginx/conf.d/default.conf
COPY apps/admin/dist /usr/share/nginx/html
EXPOSE 80
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-ec85b8e1]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-ec85b8e1]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-ec85b8e1]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-ec85b8e1]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-ec85b8e1]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.filters[data-v-ec85b8e1]{display:flex;gap:12px}.admin-name[data-v-ec85b8e1]{font-weight:500;color:#1e293b}.code-badge[data-v-ec85b8e1]{background:#f8fafc;border:1px solid #e2e8f0;padding:2px 6px;border-radius:4px;font-family:monospace;font-size:11px;color:#64748b}.date-cell[data-v-ec85b8e1]{color:#334155;font-size:13px}.text-muted[data-v-ec85b8e1]{color:#94a3b8;font-size:12px}.action-more-btn[data-v-ec85b8e1]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-ec85b8e1]:hover{background-color:#eef2ff!important}.json-box[data-v-ec85b8e1]{background:#0f172a;color:#38bdf8;padding:14px;border-radius:8px;font-family:monospace;font-size:12px;max-height:240px;overflow-y:auto;direction:ltr;text-align:left;border:1px solid #1e293b}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.wheel-picker-grid[data-v-31298959]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;margin-top:10px}.design-card[data-v-31298959]{border:2px solid #e2e8f0;border-radius:12px;overflow:hidden;cursor:pointer;transition:all .25s ease;background:#fff;display:flex;flex-direction:column}.design-card[data-v-31298959]:hover{border-color:#1677ff;transform:translateY(-3px);box-shadow:0 8px 20px #0000001a}.design-card.is-selected[data-v-31298959]{border-color:#1677ff;box-shadow:0 0 0 2px #1677ff40}.preview-stage[data-v-31298959]{height:140px;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;border-bottom:1px solid rgba(0,0,0,.08)}.theme-tag[data-v-31298959]{position:absolute;top:10px;right:10px;z-index:10;margin:0;font-weight:600}.stage-tools[data-v-31298959]{position:absolute;bottom:8px;left:8px;display:flex;gap:6px;z-index:10}.tool-btn[data-v-31298959]{background:#ffffffd9;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border:none;box-shadow:0 2px 6px #00000026}.tool-btn[data-v-31298959]:hover{background:#fff;color:#1677ff}.wheel-wrapper[data-v-31298959]{position:relative;width:96px;height:96px;display:flex;align-items:center;justify-content:center}.pointer-wrapper[data-v-31298959]{position:absolute;top:-8px;left:50%;transform:translate(-50%);z-index:20;filter:drop-shadow(0 2px 4px rgba(0,0,0,.4))}.mini-wheel-svg[data-v-31298959]{width:100%;height:100%;border-radius:50%;filter:drop-shadow(0 4px 10px rgba(0,0,0,.3))}.card-body[data-v-31298959]{padding:14px 16px;flex:1;display:flex;flex-direction:column}.card-title-row[data-v-31298959]{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.title-text[data-v-31298959]{font-weight:700;font-size:13.5px;color:#1e293b}.selected-icon[data-v-31298959]{color:#1677ff;font-size:18px}.card-desc[data-v-31298959]{font-size:11.5px;color:#64748b;line-height:1.6;margin:0}.modal-preview-container[data-v-31298959]{padding:30px 20px;border-radius:12px;display:flex;flex-direction:column;align-items:center}.modal-wheel-box[data-v-31298959]{position:relative;width:240px;height:240px}.large-pointer[data-v-31298959]{position:absolute;top:-12px;left:50%;transform:translate(-50%);z-index:20;filter:drop-shadow(0 3px 6px rgba(0,0,0,.5))}.large-wheel-svg[data-v-31298959]{width:100%;height:100%;border-radius:50%;filter:drop-shadow(0 8px 24px rgba(0,0,0,.4))}.submit-btn[data-v-c11ffb22]{font-weight:600;border-radius:8px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-d90ae09e]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-d90ae09e]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-d90ae09e]{font-size:13px;color:#64748b;margin:4px 0 0}.create-btn[data-v-d90ae09e]{font-weight:600;border-radius:8px;box-shadow:0 2px 4px #4f46e533}.main-card[data-v-d90ae09e]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-d90ae09e]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;flex-wrap:wrap;gap:12px}.filters[data-v-d90ae09e]{display:flex;gap:12px}.campaign-title[data-v-d90ae09e]{font-weight:600;color:#1e293b;cursor:pointer;transition:color .15s ease}.campaign-title[data-v-d90ae09e]:hover{color:#4f46e5}.slug-badge[data-v-d90ae09e]{background:#f8fafc;border:1px solid #e2e8f0;padding:2px 8px;border-radius:6px;color:#475569;font-size:12px;font-family:monospace}.date-cell[data-v-d90ae09e],.num-cell[data-v-d90ae09e]{color:#334155;font-size:13px}.status-tag[data-v-d90ae09e]{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:20px;font-weight:500}.status-dot[data-v-d90ae09e]{width:6px;height:6px;border-radius:50%;display:inline-block}.action-more-btn[data-v-d90ae09e]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-d90ae09e]:hover{color:#4f46e5!important;background-color:#eef2ff!important}.dropdown-action-item[data-v-d90ae09e]{display:flex;align-items:center;gap:8px;color:#334155;text-decoration:none;font-size:13px}.dropdown-item-icon[data-v-d90ae09e]{font-size:14px}.text-success[data-v-d90ae09e]{color:#10b981}.text-warning[data-v-d90ae09e]{color:#f59e0b}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{c as f,I as u}from"./index-Dx9ODHpC.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 310H732.4c13.6-21.4 21.6-46.8 21.6-74 0-76.1-61.9-138-138-138-41.4 0-78.7 18.4-104 47.4-25.3-29-62.6-47.4-104-47.4-76.1 0-138 61.9-138 138 0 27.2 7.9 52.6 21.6 74H144c-17.7 0-32 14.3-32 32v200c0 4.4 3.6 8 8 8h40v344c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V550h40c4.4 0 8-3.6 8-8V342c0-17.7-14.3-32-32-32zm-334-74c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70h-70v-70zm-138-70c38.6 0 70 31.4 70 70v70h-70c-38.6 0-70-31.4-70-70s31.4-70 70-70zM180 482V378h298v104H180zm48 68h250v308H228V550zm568 308H546V550h250v308zm48-376H546V378h298v104z"}}]},name:"gift",theme:"outlined"};function a(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(c){return Object.getOwnPropertyDescriptor(e,c).enumerable}))),n.forEach(function(c){o(r,c,e[c])})}return r}function o(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var i=function(t,e){var n=a({},t,e.attrs);return f(u,a({},n,{icon:l}),null)};i.displayName="GiftOutlined";i.inheritAttrs=!1;export{i as G};
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
import{c as i,I as l}from"./index-Dx9ODHpC.js";var u={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240zm460 600H232V536h560v304zM484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53a48.01 48.01 0 10-56 0z"}}]},name:"lock",theme:"outlined"};function a(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(c){return Object.getOwnPropertyDescriptor(e,c).enumerable}))),n.forEach(function(c){f(r,c,e[c])})}return r}function f(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=a({},t,e.attrs);return i(l,a({},n,{icon:u}),null)};o.displayName="LockOutlined";o.inheritAttrs=!1;export{o as L};
+1
View File
@@ -0,0 +1 @@
.login-container[data-v-20e1793e]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle at 50% 20%,#1e1b4b,#0f172a 70%,#020617);padding:24px;position:relative;overflow:hidden}.login-bg-decorations[data-v-20e1793e]{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;overflow:hidden}.decor-circle[data-v-20e1793e]{position:absolute;border-radius:50%;filter:blur(80px);opacity:.15}.circle-1[data-v-20e1793e]{width:400px;height:400px;background:#6366f1;top:10%;left:20%}.circle-2[data-v-20e1793e]{width:350px;height:350px;background:#a855f7;bottom:10%;right:20%}.login-card[data-v-20e1793e]{width:100%;max-width:420px;background:#fff;border-radius:16px;padding:40px 36px;box-shadow:0 25px 50px -12px #00000073,0 0 0 1px #ffffff1a;position:relative;z-index:1}.header[data-v-20e1793e]{text-align:center;margin-bottom:32px}.logo-wrapper[data-v-20e1793e]{width:68px;height:68px;margin:0 auto 16px;background:#eef2ff;border-radius:16px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px #4f46e51f}.logo[data-v-20e1793e]{font-size:34px;line-height:1}h1[data-v-20e1793e]{font-size:20px;font-weight:700;margin:0;color:#0f172a;letter-spacing:-.3px}p[data-v-20e1793e]{font-size:13px;color:#64748b;margin-top:6px;margin-bottom:0}.login-submit-btn[data-v-20e1793e]{height:44px;font-size:15px;font-weight:600;border-radius:8px;background:#4f46e5;box-shadow:0 4px 14px #4f46e559;transition:all .2s ease}.login-submit-btn[data-v-20e1793e]:hover{background:#4338ca!important;box-shadow:0 6px 18px #4f46e573}
+1
View File
@@ -0,0 +1 @@
import{c as l,I as h,d as O,u as x,a as V,b as n,w as i,r as c,o as S,e as p,f as z,g as L,h as M,i as P,_ as j}from"./index-Dx9ODHpC.js";import{L as B}from"./LockOutlined-ihCI0sD8.js";var N={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 110.8V792H136V270.8l-27.6-21.5 39.3-50.5 42.8 33.3h643.1l42.8-33.3 39.3 50.5-27.7 21.5zM833.6 232L512 482 190.4 232l-42.8-33.3-39.3 50.5 27.6 21.5 341.6 265.6a55.99 55.99 0 0068.7 0L888 270.8l27.6-21.5-39.3-50.5-42.7 33.2z"}}]},name:"mail",theme:"outlined"};function f(r){for(var a=1;a<arguments.length;a++){var t=arguments[a]!=null?Object(arguments[a]):{},e=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(e=e.concat(Object.getOwnPropertySymbols(t).filter(function(s){return Object.getOwnPropertyDescriptor(t,s).enumerable}))),e.forEach(function(s){k(r,s,t[s])})}return r}function k(r,a,t){return a in r?Object.defineProperty(r,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[a]=t,r}var m=function(a,t){var e=f({},a,t.attrs);return l(h,f({},e,{icon:N}),null)};m.displayName="MailOutlined";m.inheritAttrs=!1;const A={class:"login-container"},C={class:"login-card"},I=O({__name:"LoginView",setup(r){const a=P(),t=x(),e=L({email:"admin@luckywheel.local",password:"Admin@SecurePass2026"}),s=M(!1);async function _(){s.value=!0;try{await t.login({email:e.email,password:e.password}),a.push("/dashboard")}catch{}finally{s.value=!1}}return(g,o)=>{const v=c("a-input"),u=c("a-form-item"),b=c("a-input-password"),w=c("a-button"),y=c("a-form");return S(),V("div",A,[o[4]||(o[4]=n("div",{class:"login-bg-decorations"},[n("div",{class:"decor-circle circle-1"}),n("div",{class:"decor-circle circle-2"})],-1)),n("div",C,[o[3]||(o[3]=n("div",{class:"header"},[n("div",{class:"logo-wrapper"},[n("span",{class:"logo"},"🎡")]),n("h1",null,"ورود به پنل مدیریت"),n("p",null,"سامانه گردونه شانس و قرعه‌کشی")],-1)),l(y,{model:e,layout:"vertical",onFinish:_},{default:i(()=>[l(u,{label:"ایمیل مدیریت",name:"email",rules:[{required:!0,message:"لطفاً ایمیل را وارد کنید"},{type:"email",message:"فرمت ایمیل نامعتبر است"}]},{default:i(()=>[l(v,{value:e.email,"onUpdate:value":o[0]||(o[0]=d=>e.email=d),size:"large",placeholder:"admin@example.com"},{prefix:i(()=>[l(p(m),{style:{color:"#6366f1"}})]),_:1},8,["value"])]),_:1}),l(u,{label:"کلمه عبور",name:"password",rules:[{required:!0,message:"لطفاً کلمه عبور را وارد کنید"}]},{default:i(()=>[l(b,{value:e.password,"onUpdate:value":o[1]||(o[1]=d=>e.password=d),size:"large",placeholder:"••••••••"},{prefix:i(()=>[l(p(B),{style:{color:"#6366f1"}})]),_:1},8,["value"])]),_:1}),l(u,{style:{"margin-top":"28px"}},{default:i(()=>[l(w,{type:"primary","html-type":"submit",size:"large",block:"",loading:s.value,class:"login-submit-btn"},{default:i(()=>[...o[2]||(o[2]=[z(" ورود به پنل ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])])])}}}),H=j(I,[["__scopeId","data-v-20e1793e"]]);export{H as default};
+1
View File
@@ -0,0 +1 @@
import{c as u,I as l}from"./index-Dx9ODHpC.js";var c={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M456 231a56 56 0 10112 0 56 56 0 10-112 0zm0 280a56 56 0 10112 0 56 56 0 10-112 0zm0 280a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"more",theme:"outlined"};function o(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(t).filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable}))),n.forEach(function(a){f(r,a,t[a])})}return r}function f(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var i=function(e,t){var n=o({},e,t.attrs);return u(l,o({},n,{icon:c}),null)};i.displayName="MoreOutlined";i.inheritAttrs=!1;export{i as M};
@@ -0,0 +1 @@
import{c as u,I as l}from"./index-Dx9ODHpC.js";var o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm-88-532h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8zm224 0h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8z"}}]},name:"pause-circle",theme:"outlined"};function a(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(t).filter(function(c){return Object.getOwnPropertyDescriptor(t,c).enumerable}))),n.forEach(function(c){s(r,c,t[c])})}return r}function s(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var i=function(e,t){var n=a({},e,t.attrs);return u(l,a({},n,{icon:o}),null)};i.displayName="PauseCircleOutlined";i.inheritAttrs=!1;export{i as P};
+1
View File
@@ -0,0 +1 @@
import{c as i,I as u}from"./index-Dx9ODHpC.js";var s={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"plus-circle",theme:"outlined"};function a(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(t).filter(function(c){return Object.getOwnPropertyDescriptor(t,c).enumerable}))),n.forEach(function(c){o(r,c,t[c])})}return r}function o(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var l=function(e,t){var n=a({},e,t.attrs);return i(u,a({},n,{icon:s}),null)};l.displayName="PlusCircleOutlined";l.inheritAttrs=!1;export{l as P};
+1
View File
@@ -0,0 +1 @@
.action-more-btn[data-v-b0ebd2d6]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-b0ebd2d6]:hover{color:#4f46e5!important;background-color:#eef2ff!important}.dropdown-action-item[data-v-b0ebd2d6]{display:flex;align-items:center;gap:8px;color:#334155;text-decoration:none;font-size:13px}.dropdown-item-icon[data-v-b0ebd2d6]{font-size:14px}.text-danger[data-v-b0ebd2d6]{color:#ef4444}.prize-modal-form[data-v-b0ebd2d6] .ant-form-item{margin-bottom:12px}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-ee1ef293]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-ee1ef293]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-ee1ef293]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-ee1ef293]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.kpi-card[data-v-ee1ef293]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9;transition:all .25s ease}.kpi-card[data-v-ee1ef293]:hover{transform:translateY(-2px);box-shadow:0 6px 16px #00000012}.kpi-flex[data-v-ee1ef293]{display:flex;align-items:center;gap:16px}.kpi-icon-box[data-v-ee1ef293]{width:52px;height:52px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0}.icon-indigo[data-v-ee1ef293]{background:#eef2ff;color:#4f46e5}.icon-sky[data-v-ee1ef293]{background:#f0f9ff;color:#0284c7}.icon-emerald[data-v-ee1ef293]{background:#ecfdf5;color:#059669}.icon-purple[data-v-ee1ef293]{background:#faf5ff;color:#9333ea}.icon-amber[data-v-ee1ef293]{background:#fffbeb;color:#d97706}.icon-rose[data-v-ee1ef293]{background:#fff1f2;color:#e11d48}.icon-teal[data-v-ee1ef293]{background:#f0fdfa;color:#0d9488}.kpi-details[data-v-ee1ef293]{display:flex;flex-direction:column}.kpi-label[data-v-ee1ef293]{font-size:13px;color:#64748b;font-weight:500}.kpi-value[data-v-ee1ef293]{font-size:22px;font-weight:700;color:#0f172a;line-height:1.2;margin:2px 0 0}.filters-header[data-v-ee1ef293]{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}.filters[data-v-ee1ef293]{display:flex;gap:12px}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-1ae7a208]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-1ae7a208]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-1ae7a208]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-1ae7a208]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-1ae7a208]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.filters[data-v-1ae7a208]{display:flex;gap:12px}.code-badge[data-v-1ae7a208]{background:#f8fafc;border:1px solid #e2e8f0;padding:2px 6px;border-radius:4px;font-family:monospace;font-size:11px;color:#64748b}.num-cell[data-v-1ae7a208],.date-cell[data-v-1ae7a208]{color:#334155;font-size:13px}.text-danger[data-v-1ae7a208]{color:#ef4444;font-size:12px}.text-muted[data-v-1ae7a208]{color:#94a3b8}.status-tag[data-v-1ae7a208]{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:20px;font-weight:500}.status-dot[data-v-1ae7a208]{width:6px;height:6px;border-radius:50%;display:inline-block}.action-more-btn[data-v-1ae7a208]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-1ae7a208]:hover{background-color:#eef2ff!important}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-aaf12675]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-aaf12675]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-aaf12675]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-aaf12675]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-aaf12675]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;flex-wrap:wrap;gap:12px}.filters[data-v-aaf12675]{display:flex;gap:12px}.mobile-cell[data-v-aaf12675]{font-weight:600;color:#1e293b}.user-mobile-link[data-v-aaf12675]{cursor:pointer;transition:color .15s ease}.user-mobile-link[data-v-aaf12675]:hover{color:#4f46e5}.user-name[data-v-aaf12675],.prize-text[data-v-aaf12675]{font-weight:500;color:#1e293b}.code-badge[data-v-aaf12675]{background:#f8fafc;border:1px solid #e2e8f0;padding:2px 6px;border-radius:4px;font-family:monospace;font-size:11px;color:#64748b}.num-cell[data-v-aaf12675],.date-cell[data-v-aaf12675]{color:#334155;font-size:13px}.text-muted[data-v-aaf12675]{color:#94a3b8}.status-tag[data-v-aaf12675]{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:20px;font-weight:500}.status-dot[data-v-aaf12675]{width:6px;height:6px;border-radius:50%;display:inline-block}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{c as u,I as l}from"./index-Dx9ODHpC.js";var o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M824.2 699.9a301.55 301.55 0 00-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 00-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 008 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 01612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 008-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 01-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 01612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 01-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 008 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"}}]},name:"team",theme:"outlined"};function c(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},a=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(a=a.concat(Object.getOwnPropertySymbols(t).filter(function(n){return Object.getOwnPropertyDescriptor(t,n).enumerable}))),a.forEach(function(n){f(r,n,t[n])})}return r}function f(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var i=function(e,t){var a=c({},e,t.attrs);return u(l,c({},a,{icon:o}),null)};i.displayName="TeamOutlined";i.inheritAttrs=!1;export{i as T};
+1
View File
@@ -0,0 +1 @@
import{c as o,I as i}from"./index-Dx9ODHpC.js";var u={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){f(r,a,e[a])})}return r}function f(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var l=function(t,e){var n=c({},t,e.attrs);return o(i,c({},n,{icon:u}),null)};l.displayName="UploadOutlined";l.inheritAttrs=!1;export{l as U};
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.detail-header[data-v-331e5087]{background:#fff;margin-bottom:20px;border-radius:12px;padding:16px 24px;border:1px solid #f1f5f9;box-shadow:0 1px 3px #0000000a}.section-card[data-v-331e5087]{border-radius:12px;border:1px solid #f1f5f9;box-shadow:0 1px 3px #0000000a}.detail-tabs[data-v-331e5087]{background:#fff;padding:16px;border-radius:12px;border:1px solid #f1f5f9;box-shadow:0 1px 3px #0000000a}.code-badge[data-v-331e5087]{background:#f1f5f9;padding:2px 6px;border-radius:4px;font-family:monospace;font-size:12px;color:#334155}.num-cell[data-v-331e5087],.date-cell[data-v-331e5087]{color:#334155;font-size:13px}.action-text-btn[data-v-331e5087]{color:#4f46e5!important;font-size:12px;font-weight:500;border-radius:6px;transition:all .2s ease}.action-text-btn[data-v-331e5087]:hover{background-color:#eef2ff!important}.text-muted[data-v-331e5087]{color:#cbd5e1}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-fd1c9633]{margin-bottom:20px}.page-header-title .title[data-v-fd1c9633]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-fd1c9633]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-fd1c9633]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-fd1c9633]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.filters[data-v-fd1c9633]{display:flex;gap:12px}.user-mobile-link[data-v-fd1c9633]{font-weight:600;color:#1e293b;cursor:pointer;transition:color .15s ease}.user-mobile-link[data-v-fd1c9633]:hover{color:#4f46e5}.user-name[data-v-fd1c9633]{font-weight:500;color:#334155}.num-cell[data-v-fd1c9633],.date-cell[data-v-fd1c9633]{color:#334155;font-size:13px}.status-tag[data-v-fd1c9633]{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:20px;font-weight:500}.status-dot[data-v-fd1c9633]{width:6px;height:6px;border-radius:50%;display:inline-block}.action-more-btn[data-v-fd1c9633]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-fd1c9633]:hover{color:#4f46e5!important;background-color:#eef2ff!important}.dropdown-action-item[data-v-fd1c9633]{display:flex;align-items:center;gap:8px;color:#334155;text-decoration:none;font-size:13px}.dropdown-item-icon[data-v-fd1c9633]{font-size:14px}.text-success[data-v-fd1c9633]{color:#10b981}.text-danger[data-v-fd1c9633]{color:#ef4444}
+1
View File
@@ -0,0 +1 @@
.page-header-title[data-v-b623e0ea]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px}.page-header-title .title[data-v-b623e0ea]{font-size:20px;font-weight:700;color:#0f172a;margin:0;letter-spacing:-.3px}.page-header-title .subtitle[data-v-b623e0ea]{font-size:13px;color:#64748b;margin:4px 0 0}.main-card[data-v-b623e0ea]{border-radius:12px;box-shadow:0 1px 3px #0000000a;border:1px solid #f1f5f9}.table-header[data-v-b623e0ea]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;flex-wrap:wrap;gap:12px}.filters[data-v-b623e0ea]{display:flex;gap:12px}.mobile-cell[data-v-b623e0ea]{font-weight:600;color:#1e293b}.user-link[data-v-b623e0ea]{cursor:pointer;transition:color .15s ease}.user-link[data-v-b623e0ea]:hover{color:#4f46e5}.user-name[data-v-b623e0ea]{font-weight:500;color:#334155}.campaign-cell[data-v-b623e0ea]{color:#475569}.prize-text[data-v-b623e0ea]{font-weight:600;color:#1e293b}.code-badge[data-v-b623e0ea]{background:#f8fafc;border:1px solid #e2e8f0;padding:2px 6px;border-radius:4px;font-family:monospace;font-size:12px;color:#475569}.clickable-code[data-v-b623e0ea]{cursor:pointer;transition:all .15s ease}.clickable-code[data-v-b623e0ea]:hover{background:#eef2ff;border-color:#c7d2fe;color:#4f46e5}.date-cell[data-v-b623e0ea]{color:#334155;font-size:13px}.text-muted[data-v-b623e0ea]{color:#94a3b8}.status-tag[data-v-b623e0ea]{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:20px;font-weight:500}.status-dot[data-v-b623e0ea]{width:6px;height:6px;border-radius:50%;display:inline-block}.action-more-btn[data-v-b623e0ea]{color:#64748b;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;transition:all .2s ease}.action-more-btn[data-v-b623e0ea]:hover{color:#4f46e5!important;background-color:#eef2ff!important}.dropdown-action-item[data-v-b623e0ea]{display:flex;align-items:center;gap:8px;color:#334155;text-decoration:none;font-size:13px}.dropdown-item-icon[data-v-b623e0ea]{font-size:14px}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
const i=["۰","۱","۲","۳","۴","۵","۶","۷","۸","۹"];function o(r){return r==null||typeof r=="object"&&r!==null&&"value"in r&&(r=r.value,r==null)||typeof r=="object"?"":String(r).replace(/[0-9]/g,t=>i[+t]).replace(/[٠-٩]/g,t=>i[t.charCodeAt(0)-1632])}function f(r){if(r==null||r===""||typeof r=="object"&&r!==null&&"value"in r&&(r=r.value,r==null||r==="")||typeof r=="object")return"";const t=typeof r=="string"?parseFloat(u(r)):r;if(isNaN(t))return o(r);const e=new Intl.NumberFormat("fa-IR").format(t);return o(e)}function u(r){return r==null?"":String(r).replace(/[۰-۹]/g,t=>String(t.charCodeAt(0)-1776)).replace(/[٠-٩]/g,t=>String(t.charCodeAt(0)-1632))}function s(r,t=0){if(r==null||r==="")return t;if(typeof r=="number")return isNaN(r)?t:r;const e=u(r).replace(/[^0-9.-]/g,""),n=Number(e);return isNaN(n)?t:n}export{u as a,f,s as p,o as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>پنل مدیریت گردونه شانس و قرعه‌کشی</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
margin: 0;
background-color: #f0f2f5;
direction: rtl;
}
</style>
<script type="module" crossorigin src="/assets/index-Dx9ODHpC.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C1RGR6WN.css">
</head>
<body>
<div id="app"></div>␍
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public";
}
}
+4 -2
View File
@@ -12,8 +12,10 @@ declare module 'axios' {
}
}
const apiBaseURL = import.meta.env.VITE_API_BASE_URL || '/api';
export const apiClient = axios.create({
baseURL: '/api',
baseURL: apiBaseURL,
headers: {
'Content-Type': 'application/json',
},
@@ -96,7 +98,7 @@ apiClient.interceptors.response.use(
}
try {
const { data } = await axios.post('/api/admin/auth/refresh', { refreshToken });
const { data } = await axios.post(`${apiBaseURL}/admin/auth/refresh`, { refreshToken });
const newToken = data.data?.accessToken || data.accessToken;
localStorage.setItem('admin_access_token', newToken);
apiClient.defaults.headers.common.Authorization = `Bearer ${newToken}`;
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_BASE_URL?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+10 -1
View File
@@ -6,6 +6,7 @@ PORT=3000
DATABASE_URL="postgresql://postgres:postgrespassword@localhost:5432/lucky_wheel?schema=public"
# Redis & Queue
# اگر از docker-compose لوکال استفاده می‌کنید و پورت 6380 مپ شده: REDIS_PORT=6380
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
@@ -30,6 +31,14 @@ STORAGE_DRIVER=local
STORAGE_LOCAL_ROOT=./uploads
STORAGE_PUBLIC_URL=http://localhost:3000/uploads
# CORS
# CORS (لوکال)
CORS_ADMIN_ORIGIN=http://localhost:5173
CORS_CLIENT_ORIGIN=http://localhost:5174
# --- Production (Dokploy) نمونه ---
# CORS_ADMIN_ORIGIN=https://adm.wheel.montzar.ir
# CORS_CLIENT_ORIGIN=https://wheel.montzar.ir
# STORAGE_PUBLIC_URL=https://api.wheel.montzar.ir/uploads
# DATABASE_URL=postgresql://postgres:PASSWORD@postgres:5432/lucky_wheel?schema=public
# REDIS_HOST=redis
# REDIS_PORT=6379
+15 -12
View File
@@ -1,22 +1,25 @@
FROM node:20-alpine AS builder
FROM docker.arvancloud.ir/node:20-alpine AS production
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable pnpm
# تصویر نهایی: release را لوکال بسازید:
# pnpm --filter @lucky-wheel/api build:release
# سپس فقط کپی می‌شود (بدون nest build داخل ایمیج).
WORKDIR /app
COPY . .
ENV NODE_ENV=production
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @lucky-wheel/api build
RUN pnpm deploy --filter @lucky-wheel/api --prod /prod/api
RUN apk add --no-cache openssl libc6-compat python3 make g++
FROM node:20-alpine AS runner
COPY apps/api/release/package.json ./
COPY apps/api/release/prisma ./prisma
COPY apps/api/release/app.js ./
WORKDIR /app
COPY --from=builder /prod/api .
RUN npm install --omit=dev \
&& npx prisma generate \
&& apk del python3 make g++
RUN mkdir -p uploads
EXPOSE 3000
CMD ["node", "dist/src/main"]
CMD ["sh", "-c", "npx prisma migrate deploy && node app.js"]
+25
View File
@@ -0,0 +1,25 @@
/**
* باندل تک‌فایل برای Docker (release/app.js) بعد از nest build.
* Prisma و bcrypt به‌خاطر باینری native خارجی می‌مانند و در ایمیج نصب می‌شوند.
*/
module.exports = {
entryPoints: ['dist/src/main.js'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'cjs',
outfile: 'release/app.js',
sourcemap: false,
keepNames: true,
legalComments: 'none',
external: [
'@prisma/client',
'.prisma/client',
'bcrypt',
// optional Nest peers — نباید resolve اجباری شوند
'@nestjs/microservices',
'@nestjs/websockets',
'@nestjs/platform-socket.io',
'class-transformer/storage',
],
};
+3 -1
View File
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"build": "nest build",
"build:release": "prisma generate && nest build && node scripts/bundle-release.cjs",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
@@ -64,7 +65,8 @@
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"esbuild": "^0.25.0"
},
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
+3 -1
View File
@@ -1,5 +1,7 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
// native = ماشین بیلد لوکال؛ linux-musl = Alpine داخل Docker/Dokploy
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {
+217327
View File
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
{
"name": "lucky-wheel-api-release",
"private": true,
"version": "1.0.0",
"dependencies": {
"@prisma/client": "5.22.0",
"bcrypt": "5.1.1",
"prisma": "5.22.0"
}
}
@@ -0,0 +1,320 @@
-- CreateEnum
CREATE TYPE "AdminStatus" AS ENUM ('ACTIVE', 'DISABLED');
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'OPERATOR', 'SUPPORT');
-- CreateEnum
CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'BLOCKED');
-- CreateEnum
CREATE TYPE "CampaignStatus" AS ENUM ('DRAFT', 'ACTIVE', 'PAUSED', 'FINISHED');
-- CreateEnum
CREATE TYPE "PrizeType" AS ENUM ('PHYSICAL', 'DISCOUNT_CODE', 'CREDIT', 'CASH', 'NO_PRIZE', 'OTHER');
-- CreateEnum
CREATE TYPE "StockType" AS ENUM ('LIMITED', 'UNLIMITED');
-- CreateEnum
CREATE TYPE "PrizeStatus" AS ENUM ('ACTIVE', 'INACTIVE');
-- CreateEnum
CREATE TYPE "SpinStatus" AS ENUM ('SUCCESS', 'FAILED', 'DUPLICATE');
-- CreateEnum
CREATE TYPE "ChanceTxnType" AS ENUM ('SIGNUP_BONUS', 'ADMIN_ADJUSTMENT', 'SPIN_CONSUME', 'CAMPAIGN_BONUS', 'REFUND');
-- CreateEnum
CREATE TYPE "UserPrizeStatus" AS ENUM ('NEW', 'CONTACTED', 'DELIVERED', 'ACTIVE_CODE', 'USED', 'EXPIRED');
-- CreateEnum
CREATE TYPE "SmsType" AS ENUM ('OTP', 'WINNING', 'NO_PRIZE', 'PRIZE_REMINDER');
-- CreateEnum
CREATE TYPE "SmsStatus" AS ENUM ('PENDING', 'SENT', 'FAILED');
-- CreateEnum
CREATE TYPE "WheelDesign" AS ENUM ('CLASSIC_CASINO', 'NEON_CYBER', 'MINIMAL_FLAT', 'LUXURY_GOLD', 'PLAYFUL_CARTOON');
-- CreateTable
CREATE TABLE "admins" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL DEFAULT 'OPERATOR',
"status" "AdminStatus" NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "admins_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"name" TEXT,
"status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
"last_login_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "otp_codes" (
"id" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"code" TEXT NOT NULL,
"user_id" TEXT,
"attempts" INTEGER NOT NULL DEFAULT 0,
"consumed" BOOLEAN NOT NULL DEFAULT false,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "otp_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "campaigns" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"description" TEXT,
"image" TEXT,
"banner" TEXT,
"logo" TEXT,
"start_at" TIMESTAMP(3) NOT NULL,
"end_at" TIMESTAMP(3) NOT NULL,
"status" "CampaignStatus" NOT NULL DEFAULT 'DRAFT',
"default_chances" INTEGER NOT NULL DEFAULT 0,
"rules" TEXT,
"cta_text" TEXT,
"wheel_design" "WheelDesign" NOT NULL DEFAULT 'CLASSIC_CASINO',
"settings" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "campaigns_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "prizes" (
"id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"image" TEXT,
"type" "PrizeType" NOT NULL,
"stock_type" "StockType" NOT NULL,
"initial_stock" INTEGER,
"remaining_stock" INTEGER,
"weight" INTEGER NOT NULL,
"status" "PrizeStatus" NOT NULL DEFAULT 'ACTIVE',
"sort_order" INTEGER NOT NULL DEFAULT 0,
"segment_color" TEXT NOT NULL,
"expires_at" TIMESTAMP(3),
"sms_template" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "prizes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_campaigns" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"total_chances" INTEGER NOT NULL DEFAULT 0,
"used_chances" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_campaigns_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "chance_transactions" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"amount" INTEGER NOT NULL,
"type" "ChanceTxnType" NOT NULL,
"reference_type" TEXT,
"reference_id" TEXT,
"description" TEXT,
"created_by" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "chance_transactions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "spins" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"prize_id" TEXT,
"status" "SpinStatus" NOT NULL DEFAULT 'SUCCESS',
"idempotency_key" TEXT NOT NULL,
"ip" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "spins_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_prizes" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"prize_id" TEXT NOT NULL,
"spin_id" TEXT NOT NULL,
"code" TEXT,
"status" "UserPrizeStatus" NOT NULL DEFAULT 'NEW',
"won_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"redeemed_at" TIMESTAMP(3),
CONSTRAINT "user_prizes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sms_logs" (
"id" TEXT NOT NULL,
"user_id" TEXT,
"mobile" TEXT NOT NULL,
"type" "SmsType" NOT NULL,
"message" TEXT NOT NULL,
"provider" TEXT NOT NULL DEFAULT 'raygansms',
"status" "SmsStatus" NOT NULL DEFAULT 'PENDING',
"provider_response" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "sms_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "admin_logs" (
"id" TEXT NOT NULL,
"admin_id" TEXT NOT NULL,
"action" TEXT NOT NULL,
"entity_type" TEXT NOT NULL,
"entity_id" TEXT NOT NULL,
"old_value" JSONB,
"new_value" JSONB,
"ip" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "admin_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "admins_mobile_key" ON "admins"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "admins_email_key" ON "admins"("email");
-- CreateIndex
CREATE UNIQUE INDEX "users_mobile_key" ON "users"("mobile");
-- CreateIndex
CREATE INDEX "users_mobile_idx" ON "users"("mobile");
-- CreateIndex
CREATE INDEX "otp_codes_mobile_consumed_idx" ON "otp_codes"("mobile", "consumed");
-- CreateIndex
CREATE UNIQUE INDEX "campaigns_slug_key" ON "campaigns"("slug");
-- CreateIndex
CREATE INDEX "campaigns_status_start_at_end_at_idx" ON "campaigns"("status", "start_at", "end_at");
-- CreateIndex
CREATE INDEX "prizes_campaign_id_status_idx" ON "prizes"("campaign_id", "status");
-- CreateIndex
CREATE UNIQUE INDEX "user_campaigns_user_id_campaign_id_key" ON "user_campaigns"("user_id", "campaign_id");
-- CreateIndex
CREATE INDEX "chance_transactions_user_id_campaign_id_idx" ON "chance_transactions"("user_id", "campaign_id");
-- CreateIndex
CREATE UNIQUE INDEX "spins_idempotency_key_key" ON "spins"("idempotency_key");
-- CreateIndex
CREATE INDEX "spins_user_id_campaign_id_idx" ON "spins"("user_id", "campaign_id");
-- CreateIndex
CREATE INDEX "spins_campaign_id_created_at_idx" ON "spins"("campaign_id", "created_at");
-- CreateIndex
CREATE UNIQUE INDEX "user_prizes_spin_id_key" ON "user_prizes"("spin_id");
-- CreateIndex
CREATE UNIQUE INDEX "user_prizes_code_key" ON "user_prizes"("code");
-- CreateIndex
CREATE INDEX "user_prizes_user_id_idx" ON "user_prizes"("user_id");
-- CreateIndex
CREATE INDEX "user_prizes_status_idx" ON "user_prizes"("status");
-- CreateIndex
CREATE INDEX "sms_logs_mobile_type_idx" ON "sms_logs"("mobile", "type");
-- CreateIndex
CREATE INDEX "admin_logs_entity_type_entity_id_idx" ON "admin_logs"("entity_type", "entity_id");
-- AddForeignKey
ALTER TABLE "otp_codes" ADD CONSTRAINT "otp_codes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "prizes" ADD CONSTRAINT "prizes_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_campaigns" ADD CONSTRAINT "user_campaigns_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_campaigns" ADD CONSTRAINT "user_campaigns_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "admins"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "spins" ADD CONSTRAINT "spins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "spins" ADD CONSTRAINT "spins_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "spins" ADD CONSTRAINT "spins_prize_id_fkey" FOREIGN KEY ("prize_id") REFERENCES "prizes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_prize_id_fkey" FOREIGN KEY ("prize_id") REFERENCES "prizes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_spin_id_fkey" FOREIGN KEY ("spin_id") REFERENCES "spins"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sms_logs" ADD CONSTRAINT "sms_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "admin_logs" ADD CONSTRAINT "admin_logs_admin_id_fkey" FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+315
View File
@@ -0,0 +1,315 @@
generator client {
provider = "prisma-client-js"
// native = ماشین بیلد لوکال؛ linux-musl = Alpine داخل Docker/Dokploy
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ==================== ENUMS ====================
enum AdminStatus {
ACTIVE
DISABLED
}
enum AdminRole {
SUPER_ADMIN
OPERATOR
SUPPORT
}
enum UserStatus {
ACTIVE
BLOCKED
}
enum CampaignStatus {
DRAFT
ACTIVE
PAUSED
FINISHED
}
enum PrizeType {
PHYSICAL
DISCOUNT_CODE
CREDIT
CASH
NO_PRIZE
OTHER
}
enum StockType {
LIMITED
UNLIMITED
}
enum PrizeStatus {
ACTIVE
INACTIVE
}
enum SpinStatus {
SUCCESS
FAILED
DUPLICATE
}
enum ChanceTxnType {
SIGNUP_BONUS
ADMIN_ADJUSTMENT
SPIN_CONSUME
CAMPAIGN_BONUS
REFUND
}
enum UserPrizeStatus {
NEW
CONTACTED
DELIVERED
ACTIVE_CODE
USED
EXPIRED
}
enum SmsType {
OTP
WINNING
NO_PRIZE
PRIZE_REMINDER
}
enum SmsStatus {
PENDING
SENT
FAILED
}
enum WheelDesign {
CLASSIC_CASINO
NEON_CYBER
MINIMAL_FLAT
LUXURY_GOLD
PLAYFUL_CARTOON
}
// ==================== MODELS ====================
model Admin {
id String @id @default(uuid())
name String
mobile String @unique
email String @unique
passwordHash String @map("password_hash")
role AdminRole @default(OPERATOR)
status AdminStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
auditLogs AdminLog[]
chanceTxns ChanceTransaction[] @relation("AdminCreatedTxn")
@@map("admins")
}
model User {
id String @id @default(uuid())
mobile String @unique
name String?
status UserStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userCampaigns UserCampaign[]
chanceTransactions ChanceTransaction[]
spins Spin[]
userPrizes UserPrize[]
otpCodes OtpCode[]
smsLogs SmsLog[]
@@index([mobile])
@@map("users")
}
model OtpCode {
id String @id @default(uuid())
mobile String
code String
userId String? @map("user_id")
user User? @relation(fields: [userId], references: [id])
attempts Int @default(0)
consumed Boolean @default(false)
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([mobile, consumed])
@@map("otp_codes")
}
model Campaign {
id String @id @default(uuid())
title String
slug String @unique
description String?
image String?
banner String?
logo String?
startAt DateTime @map("start_at")
endAt DateTime @map("end_at")
status CampaignStatus @default(DRAFT)
defaultChances Int @default(0) @map("default_chances")
rules String?
ctaText String? @map("cta_text")
wheelDesign WheelDesign @default(CLASSIC_CASINO) @map("wheel_design")
settings Json? // primaryColor, secondaryColor, headerText, buttonText, headerImage, celebrationEffect...
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
prizes Prize[]
userCampaigns UserCampaign[]
chanceTxns ChanceTransaction[]
spins Spin[]
userPrizes UserPrize[]
@@index([status, startAt, endAt])
@@map("campaigns")
}
model Prize {
id String @id @default(uuid())
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
title String
description String?
image String?
type PrizeType
stockType StockType @map("stock_type")
initialStock Int? @map("initial_stock")
remainingStock Int? @map("remaining_stock")
weight Int
status PrizeStatus @default(ACTIVE)
sortOrder Int @default(0) @map("sort_order")
segmentColor String @map("segment_color")
expiresAt DateTime? @map("expires_at")
smsTemplate String? @map("sms_template")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
spins Spin[]
userPrizes UserPrize[]
@@index([campaignId, status])
@@map("prizes")
}
model UserCampaign {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
totalChances Int @default(0) @map("total_chances")
usedChances Int @default(0) @map("used_chances")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, campaignId])
@@map("user_campaigns")
}
model ChanceTransaction {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
amount Int // مثبت یا منفی
type ChanceTxnType
referenceType String? @map("reference_type") // e.g. "spin", "manual"
referenceId String? @map("reference_id")
description String?
createdById String? @map("created_by") // admin id در صورت تغییر دستی
createdBy Admin? @relation("AdminCreatedTxn", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
@@index([userId, campaignId])
@@map("chance_transactions")
}
model Spin {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
prizeId String? @map("prize_id")
prize Prize? @relation(fields: [prizeId], references: [id])
status SpinStatus @default(SUCCESS)
idempotencyKey String @unique @map("idempotency_key")
ip String?
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
userPrize UserPrize?
@@index([userId, campaignId])
@@index([campaignId, createdAt])
@@map("spins")
}
model UserPrize {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
prizeId String @map("prize_id")
prize Prize @relation(fields: [prizeId], references: [id])
spinId String @unique @map("spin_id")
spin Spin @relation(fields: [spinId], references: [id])
code String? @unique
status UserPrizeStatus @default(NEW)
wonAt DateTime @default(now()) @map("won_at")
redeemedAt DateTime? @map("redeemed_at")
@@index([userId])
@@index([status])
@@map("user_prizes")
}
model SmsLog {
id String @id @default(uuid())
userId String? @map("user_id")
user User? @relation(fields: [userId], references: [id])
mobile String
type SmsType
message String
provider String @default("raygansms")
status SmsStatus @default(PENDING)
providerResponse Json? @map("provider_response")
createdAt DateTime @default(now()) @map("created_at")
@@index([mobile, type])
@@map("sms_logs")
}
model AdminLog {
id String @id @default(uuid())
adminId String @map("admin_id")
admin Admin @relation(fields: [adminId], references: [id])
action String // e.g. "prize.update", "chance.add"
entityType String @map("entity_type")
entityId String @map("entity_id")
oldValue Json? @map("old_value")
newValue Json? @map("new_value")
ip String?
createdAt DateTime @default(now()) @map("created_at")
@@index([entityType, entityId])
@@map("admin_logs")
}
+52
View File
@@ -0,0 +1,52 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.upsert({
where: { mobile: '09382507041' },
create: {
mobile: '09382507041',
name: 'کاربر ویژه',
status: 'ACTIVE',
},
update: {
status: 'ACTIVE',
},
});
const campaign = await prisma.campaign.findUnique({
where: { slug: 'default-campaign' },
});
if (campaign) {
await prisma.userCampaign.upsert({
where: {
userId_campaignId: {
userId: user.id,
campaignId: campaign.id,
},
},
create: {
userId: user.id,
campaignId: campaign.id,
totalChances: 10,
usedChances: 0,
},
update: {
totalChances: { increment: 10 },
},
});
}
console.log('SUCCESS: User 09382507041 is authorized and ready with 10 chances!');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
+116
View File
@@ -0,0 +1,116 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding default demo campaign and prizes...');
const existingCampaign = await prisma.campaign.findUnique({
where: { slug: 'default-campaign' },
});
if (existingCampaign) {
console.log('Default campaign already exists.');
return;
}
const campaign = await prisma.campaign.create({
data: {
title: 'گردونه شانس جشنواره بزرگ نوروزی',
slug: 'default-campaign',
description: 'هر روز با چرخاندن گردونه، شانس برنده شدن آیفون، سکه طلا، و کدهای تخفیف شگفت‌انگیز را داشته باشید!',
startAt: new Date(Date.now() - 24 * 3600 * 1000),
endAt: new Date(Date.now() + 60 * 24 * 3600 * 1000),
status: 'ACTIVE',
defaultChances: 3,
ctaText: 'بچرخون و برنده شو!',
wheelDesign: 'CLASSIC_CASINO',
rules: '۱. هر کاربر در بدو ورود ۳ شانس چرخاندن رایگان دریافت می‌کند.\n۲. جوایز فیزیکی پس از هماهنگی تلفنی توسط پشتیبانی ارسال خواهد شد.\n۳. کدهای تخفیف بلافاصله پیامک شده و تا ۷ روز پس از دریافت معتبر هستند.',
prizes: {
create: [
{
title: 'آیفون ۱۵ پرو',
type: 'PHYSICAL',
stockType: 'LIMITED',
initialStock: 3,
remainingStock: 3,
weight: 1,
sortOrder: 1,
segmentColor: '#b91c1c',
},
{
title: 'تخفیف ۵۰٪ دیجی‌کالا',
type: 'DISCOUNT_CODE',
stockType: 'UNLIMITED',
weight: 25,
sortOrder: 2,
segmentColor: '#1e293b',
},
{
title: 'کارت هدیه ۵۰۰ تومانی',
type: 'CREDIT',
stockType: 'LIMITED',
initialStock: 50,
remainingStock: 50,
weight: 10,
sortOrder: 3,
segmentColor: '#b91c1c',
},
{
title: 'پوچ (دفعه بعد)',
type: 'NO_PRIZE',
stockType: 'UNLIMITED',
weight: 20,
sortOrder: 4,
segmentColor: '#1e293b',
},
{
title: 'تخفیف ۳۰٪ اسنپ',
type: 'DISCOUNT_CODE',
stockType: 'UNLIMITED',
weight: 20,
sortOrder: 5,
segmentColor: '#b91c1c',
},
{
title: 'ساعت هوشمند',
type: 'PHYSICAL',
stockType: 'LIMITED',
initialStock: 10,
remainingStock: 10,
weight: 4,
sortOrder: 6,
segmentColor: '#1e293b',
},
{
title: 'شانس مجدد',
type: 'OTHER',
stockType: 'UNLIMITED',
weight: 15,
sortOrder: 7,
segmentColor: '#b91c1c',
},
{
title: 'پوچ (بدون جایزه)',
type: 'NO_PRIZE',
stockType: 'UNLIMITED',
weight: 5,
sortOrder: 8,
segmentColor: '#1e293b',
},
],
},
},
});
console.log(`Campaign created with ID: ${campaign.id} and 8 prizes.`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
+56
View File
@@ -0,0 +1,56 @@
/**
* باندل تک‌فایل برای Docker (release/app.js) بعد از nest build.
* تنظیمات alias در ../esbuild.release.config.cjs
*/
const esbuild = require('esbuild');
const { existsSync, mkdirSync, cpSync, writeFileSync } = require('fs');
const path = require('path');
const root = path.join(__dirname, '..');
const config = require(path.join(root, 'esbuild.release.config.cjs'));
async function main() {
const entry = path.join(root, config.entryPoints[0]);
if (!existsSync(entry)) {
console.error('dist/src/main.js یافت نشد. ابتدا اجرا کنید: pnpm run build');
process.exit(1);
}
const outfile = path.join(root, config.outfile);
mkdirSync(path.dirname(outfile), { recursive: true });
await esbuild.build({
...config,
entryPoints: [entry],
outfile,
});
// Prisma schema + migrations برای generate/migrate داخل کانتینر
const prismaSrc = path.join(root, 'prisma');
const prismaDest = path.join(root, 'release', 'prisma');
cpSync(prismaSrc, prismaDest, { recursive: true });
// فقط native / runtimeهای خارجی باندل
const releasePkg = {
name: 'lucky-wheel-api-release',
private: true,
version: '1.0.0',
dependencies: {
'@prisma/client': '5.22.0',
bcrypt: '5.1.1',
prisma: '5.22.0',
},
};
writeFileSync(
path.join(root, 'release', 'package.json'),
`${JSON.stringify(releasePkg, null, 2)}\n`,
);
console.log('نوشت:', outfile);
console.log('نوشت: release/prisma + release/package.json');
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
+1
View File
@@ -0,0 +1 @@
VITE_API_BASE_URL=https://api.wheel.montzar.ir/api
+5 -25
View File
@@ -1,30 +1,10 @@
FROM node:20-alpine AS builder
FROM docker.arvancloud.ir/nginx:latest
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable pnpm
# بیلد لوکال: pnpm --filter @lucky-wheel/client build
# (با apps/client/.env.production)
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @lucky-wheel/client build
FROM nginx:alpine
COPY --from=builder /app/apps/client/dist /usr/share/nginx/html
# Configure Nginx for Single Page Application
RUN echo 'server { \
listen 80; \
server_name localhost; \
location / { \
root /usr/share/nginx/html; \
index index.html index.htm; \
try_files $uri $uri/ /index.html; \
} \
}' > /etc/nginx/conf.d/default.conf
COPY apps/client/nginx.conf /etc/nginx/conf.d/default.conf
COPY apps/client/dist /usr/share/nginx/html
EXPOSE 80
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+46
View File
@@ -0,0 +1,46 @@
import{C as a}from"./index-ilYp-WvI.js";const s=["۰","۱","۲","۳","۴","۵","۶","۷","۸","۹"];function g(e){return e==null||typeof e=="object"&&e!==null&&"value"in e&&(e=e.value,e==null)||typeof e=="object"?"":String(e).replace(/[0-9]/g,t=>s[+t]).replace(/[٠-٩]/g,t=>s[t.charCodeAt(0)-1632])}/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const p=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/var r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const y=({size:e,strokeWidth:t=2,absoluteStrokeWidth:c,color:n,iconNode:h,name:d,class:k,...u},{slots:i})=>a("svg",{...r,width:e||r.width,height:e||r.height,stroke:n||r.stroke,"stroke-width":c?Number(t)*24/Number(e):t,class:["lucide",`lucide-${p(d??"icon")}`],...u},[...h.map(l=>a(...l)),...i.default?[i.default()]:[]]);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const o=(e,t)=>(c,{slots:n})=>a(y,{...c,iconNode:t,name:e},n);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const f=o("ArrowRightIcon",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"m12 5 7 7-7 7",key:"xquz4c"}]]);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const C=o("AwardIcon",[["path",{d:"m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526",key:"1yiouv"}],["circle",{cx:"12",cy:"8",r:"6",key:"1vp47v"}]]);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const m=o("CheckIcon",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const I=o("CopyIcon",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/**
* @license lucide-vue-next v0.378.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const A=o("LoaderCircleIcon",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);export{f as A,m as C,A as L,C as a,I as b,o as c,g as t};
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎡</text></svg>" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>گردونه شانس و قرعه‌کشی</title>
<!-- Google Font Vazirmatn -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-ilYp-WvI.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BqdIdFn_.css">
</head>
<body class="bg-slate-900 text-slate-100 font-sans antialiased selection:bg-brand-500 selection:text-white overflow-x-hidden min-h-screen">
<div id="app"></div>␍
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public";
}
}
+3 -1
View File
@@ -6,8 +6,10 @@ declare module 'axios' {
}
}
const apiBaseURL = import.meta.env.VITE_API_BASE_URL || '/api';
export const apiClient = axios.create({
baseURL: '/api',
baseURL: apiBaseURL,
headers: {
'Content-Type': 'application/json',
},
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_BASE_URL?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+37
View File
@@ -0,0 +1,37 @@
# فقط Postgres + Redis برای توسعه لوکال
services:
postgres:
image: postgres:15-alpine
container_name: lucky_wheel_postgres
restart: unless-stopped
environment:
POSTGRES_DB: lucky_wheel
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgrespassword
ports:
- '5432:5432'
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres -d lucky_wheel']
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: lucky_wheel_redis
restart: unless-stopped
ports:
- '6380:6379'
volumes:
- redis_data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 5
volumes:
pg_data:
redis_data:
+46 -20
View File
@@ -1,32 +1,40 @@
# Production / Dokploy
# دامنه‌ها را در UIی Dokploy به سرویس‌ها ببندید:
# client → https://wheel.montzar.ir
# api → https://api.wheel.montzar.ir (port 3000)
# admin → https://adm.wheel.montzar.ir
#
# بیلد لوکال قبل از push:
# pnpm install && pnpm run build:release
#
# env: از .env.production.example کپی کنید و در Dokploy ست کنید.
services:
postgres:
image: postgres:15-alpine
container_name: lucky_wheel_postgres
restart: unless-stopped
environment:
POSTGRES_DB: lucky_wheel
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgrespassword
POSTGRES_DB: ${POSTGRES_DB:-lucky_wheel}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgrespassword}
ports:
- "5432:5432"
- '${EXPORT_DB_PORT:-5432}:5432'
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres -d lucky_wheel" ]
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB']
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: lucky_wheel_redis
restart: unless-stopped
ports:
- "6380:6379"
command: ['redis-server', '--appendonly', 'yes']
volumes:
- redis_data:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 5
@@ -35,15 +43,34 @@ services:
build:
context: .
dockerfile: apps/api/Dockerfile
container_name: lucky_wheel_api
restart: unless-stopped
ports:
- "3000:3000"
- '3000:3000'
environment:
- DATABASE_URL=postgresql://postgres:postgrespassword@postgres:5432/lucky_wheel?schema=public
- REDIS_HOST=redis
- REDIS_PORT=6379
- NODE_ENV=production
NODE_ENV: production
PORT: 3000
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgrespassword@postgres:5432/lucky_wheel?schema=public}
REDIS_HOST: redis
REDIS_PORT: '6379'
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
JWT_USER_SECRET: ${JWT_USER_SECRET:-change-me-user-jwt}
JWT_USER_EXPIRES_IN: ${JWT_USER_EXPIRES_IN:-7d}
JWT_ADMIN_ACCESS_SECRET: ${JWT_ADMIN_ACCESS_SECRET:-change-me-admin-access-jwt}
JWT_ADMIN_ACCESS_EXPIRES_IN: ${JWT_ADMIN_ACCESS_EXPIRES_IN:-1h}
JWT_ADMIN_REFRESH_SECRET: ${JWT_ADMIN_REFRESH_SECRET:-change-me-admin-refresh-jwt}
JWT_ADMIN_REFRESH_EXPIRES_IN: ${JWT_ADMIN_REFRESH_EXPIRES_IN:-7d}
CORS_ADMIN_ORIGIN: ${CORS_ADMIN_ORIGIN:-https://adm.wheel.montzar.ir}
CORS_CLIENT_ORIGIN: ${CORS_CLIENT_ORIGIN:-https://wheel.montzar.ir}
STORAGE_DRIVER: ${STORAGE_DRIVER:-local}
STORAGE_LOCAL_ROOT: ${STORAGE_LOCAL_ROOT:-./uploads}
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL:-https://api.wheel.montzar.ir/uploads}
SMS_DRIVER: ${SMS_DRIVER:-mock}
RAYGAN_SMS_USERNAME: ${RAYGAN_SMS_USERNAME:-}
RAYGAN_SMS_PASSWORD: ${RAYGAN_SMS_PASSWORD:-}
RAYGAN_SMS_LINE_NUMBER: ${RAYGAN_SMS_LINE_NUMBER:-}
RAYGAN_SMS_API_BASE_URL: ${RAYGAN_SMS_API_BASE_URL:-https://api.raygansms.com}
volumes:
- uploads_data:/app/uploads
depends_on:
postgres:
condition: service_healthy
@@ -54,10 +81,9 @@ services:
build:
context: .
dockerfile: apps/admin/Dockerfile
container_name: lucky_wheel_admin
restart: unless-stopped
ports:
- "8080:80"
- '8080:80'
depends_on:
- api
@@ -65,13 +91,13 @@ services:
build:
context: .
dockerfile: apps/client/Dockerfile
container_name: lucky_wheel_client
restart: unless-stopped
ports:
- "80:80"
- '8081:80'
depends_on:
- api
volumes:
pg_data:
redis_data:
uploads_data:
+6 -2
View File
@@ -9,12 +9,16 @@
"dev:client": "pnpm --filter @lucky-wheel/client dev",
"build:types": "pnpm --filter @lucky-wheel/shared-types build",
"build:api": "pnpm --filter @lucky-wheel/api build",
"build:api:release": "pnpm --filter @lucky-wheel/api build:release",
"build:admin": "pnpm --filter @lucky-wheel/admin build",
"build:client": "pnpm --filter @lucky-wheel/client build",
"build": "pnpm run build:types && pnpm --filter @lucky-wheel/api build && pnpm --filter @lucky-wheel/admin build && pnpm --filter @lucky-wheel/client build",
"build:release": "pnpm run build:types && pnpm run build:api:release && pnpm run build:admin && pnpm run build:client",
"test:api": "pnpm --filter @lucky-wheel/api test",
"docker:up": "docker-compose up -d",
"docker:down": "docker-compose down"
"docker:up": "docker-compose -f docker-compose.dev.yml up -d",
"docker:down": "docker-compose -f docker-compose.dev.yml down",
"docker:prod:up": "docker-compose up -d --build",
"docker:prod:down": "docker-compose down"
},
"devDependencies": {
"typescript": "^5.4.5"
+289 -15
View File
@@ -138,7 +138,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^10.3.2
version: 10.4.9
version: 10.4.9(esbuild@0.25.12)
'@nestjs/schematics':
specifier: ^10.1.1
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
@@ -169,6 +169,9 @@ importers:
'@types/supertest':
specifier: ^6.0.2
version: 6.0.3
esbuild:
specifier: ^0.25.0
version: 0.25.12
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3))
@@ -183,10 +186,10 @@ importers:
version: 7.2.2
ts-jest:
specifier: ^29.1.2
version: 29.4.12(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)))(typescript@5.9.3)
version: 29.4.12(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.25.12)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)))(typescript@5.9.3)
ts-loader:
specifier: ^9.5.1
version: 9.6.2(typescript@5.9.3)(webpack@5.97.1)
version: 9.6.2(typescript@5.9.3)(webpack@5.97.1(esbuild@0.25.12))
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.19.43)(typescript@5.9.3)
@@ -486,138 +489,294 @@ packages:
cpu: [ppc64]
os: [aix]
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.25.12':
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.25.12':
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.25.12':
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.25.12':
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.25.12':
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.25.12':
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.12':
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.25.12':
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.25.12':
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.25.12':
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.25.12':
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.25.12':
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.25.12':
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.25.12':
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.25.12':
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.25.12':
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.12':
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.12':
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.12':
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.12':
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.12':
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.25.12':
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.25.12':
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.25.12':
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.25.12':
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@fast-csv/format@4.3.5':
resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==}
@@ -2084,6 +2243,11 @@ packages:
engines: {node: '>=12'}
hasBin: true
esbuild@0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -4358,72 +4522,150 @@ snapshots:
'@esbuild/aix-ppc64@0.21.5':
optional: true
'@esbuild/aix-ppc64@0.25.12':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
'@esbuild/android-arm64@0.25.12':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
'@esbuild/android-arm@0.25.12':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
'@esbuild/android-x64@0.25.12':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
'@esbuild/darwin-arm64@0.25.12':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
'@esbuild/darwin-x64@0.25.12':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
'@esbuild/freebsd-arm64@0.25.12':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
'@esbuild/freebsd-x64@0.25.12':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
'@esbuild/linux-arm64@0.25.12':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
'@esbuild/linux-arm@0.25.12':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
'@esbuild/linux-ia32@0.25.12':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
'@esbuild/linux-loong64@0.25.12':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
'@esbuild/linux-mips64el@0.25.12':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
'@esbuild/linux-ppc64@0.25.12':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
'@esbuild/linux-riscv64@0.25.12':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
'@esbuild/linux-s390x@0.25.12':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
'@esbuild/linux-x64@0.25.12':
optional: true
'@esbuild/netbsd-arm64@0.25.12':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
'@esbuild/netbsd-x64@0.25.12':
optional: true
'@esbuild/openbsd-arm64@0.25.12':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
'@esbuild/openbsd-x64@0.25.12':
optional: true
'@esbuild/openharmony-arm64@0.25.12':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
'@esbuild/sunos-x64@0.25.12':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
'@esbuild/win32-arm64@0.25.12':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
'@esbuild/win32-ia32@0.25.12':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
'@esbuild/win32-x64@0.25.12':
optional: true
'@fast-csv/format@4.3.5':
dependencies:
'@types/node': 14.18.63
@@ -4713,7 +4955,7 @@ snapshots:
bullmq: 5.81.4
tslib: 2.8.1
'@nestjs/cli@10.4.9':
'@nestjs/cli@10.4.9(esbuild@0.25.12)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
'@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
@@ -4723,7 +4965,7 @@ snapshots:
chokidar: 3.6.0
cli-table3: 0.6.5
commander: 4.1.1
fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1)
fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.25.12))
glob: 10.4.5
inquirer: 8.2.6
node-emoji: 1.11.0
@@ -4732,7 +4974,7 @@ snapshots:
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.7.2
webpack: 5.97.1
webpack: 5.97.1(esbuild@0.25.12)
webpack-node-externals: 3.0.0
transitivePeerDependencies:
- '@minify-html/node'
@@ -6131,6 +6373,35 @@ snapshots:
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
esbuild@0.25.12:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.12
'@esbuild/android-arm': 0.25.12
'@esbuild/android-arm64': 0.25.12
'@esbuild/android-x64': 0.25.12
'@esbuild/darwin-arm64': 0.25.12
'@esbuild/darwin-x64': 0.25.12
'@esbuild/freebsd-arm64': 0.25.12
'@esbuild/freebsd-x64': 0.25.12
'@esbuild/linux-arm': 0.25.12
'@esbuild/linux-arm64': 0.25.12
'@esbuild/linux-ia32': 0.25.12
'@esbuild/linux-loong64': 0.25.12
'@esbuild/linux-mips64el': 0.25.12
'@esbuild/linux-ppc64': 0.25.12
'@esbuild/linux-riscv64': 0.25.12
'@esbuild/linux-s390x': 0.25.12
'@esbuild/linux-x64': 0.25.12
'@esbuild/netbsd-arm64': 0.25.12
'@esbuild/netbsd-x64': 0.25.12
'@esbuild/openbsd-arm64': 0.25.12
'@esbuild/openbsd-x64': 0.25.12
'@esbuild/openharmony-arm64': 0.25.12
'@esbuild/sunos-x64': 0.25.12
'@esbuild/win32-arm64': 0.25.12
'@esbuild/win32-ia32': 0.25.12
'@esbuild/win32-x64': 0.25.12
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -6312,7 +6583,7 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1):
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.25.12)):
dependencies:
'@babel/code-frame': 7.29.7
chalk: 4.1.2
@@ -6327,7 +6598,7 @@ snapshots:
semver: 7.8.5
tapable: 2.3.3
typescript: 5.7.2
webpack: 5.97.1
webpack: 5.97.1(esbuild@0.25.12)
form-data@4.0.6:
dependencies:
@@ -7974,13 +8245,15 @@ snapshots:
mkdirp: 1.0.4
yallist: 4.0.0
terser-webpack-plugin@5.6.1(webpack@5.97.1):
terser-webpack-plugin@5.6.1(esbuild@0.25.12)(webpack@5.97.1(esbuild@0.25.12)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
terser: 5.51.2
webpack: 5.97.1
webpack: 5.97.1(esbuild@0.25.12)
optionalDependencies:
esbuild: 0.25.12
terser@5.51.2:
dependencies:
@@ -8040,7 +8313,7 @@ snapshots:
ts-interface-checker@0.1.13: {}
ts-jest@29.4.12(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)))(typescript@5.9.3):
ts-jest@29.4.12(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.25.12)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
@@ -8058,15 +8331,16 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.29.7)
esbuild: 0.25.12
jest-util: 29.7.0
ts-loader@9.6.2(typescript@5.9.3)(webpack@5.97.1):
ts-loader@9.6.2(typescript@5.9.3)(webpack@5.97.1(esbuild@0.25.12)):
dependencies:
chalk: 4.1.2
picomatch: 4.0.7
source-map: 0.7.6
typescript: 5.9.3
webpack: 5.97.1
webpack: 5.97.1(esbuild@0.25.12)
ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3):
dependencies:
@@ -8242,7 +8516,7 @@ snapshots:
webpack-sources@3.5.1: {}
webpack@5.97.1:
webpack@5.97.1(esbuild@0.25.12):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.9
@@ -8264,7 +8538,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.3.3
terser-webpack-plugin: 5.6.1(webpack@5.97.1)
terser-webpack-plugin: 5.6.1(esbuild@0.25.12)(webpack@5.97.1(esbuild@0.25.12))
watchpack: 2.5.2
webpack-sources: 3.5.1
transitivePeerDependencies: