diff --git a/.gitignore b/.gitignore index 8b12b4b..6d94488 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ coverage/ *.exe~ *~ .DS_Store +.cursor/ +config.json +mengyastore-backend/data/ +mengyastore-frontend/public/logo.png diff --git a/API_DOCS.md b/API_DOCS.md new file mode 100644 index 0000000..8dbd354 --- /dev/null +++ b/API_DOCS.md @@ -0,0 +1,543 @@ +# 萌芽账户认证中心 API 文档 + +访问 **`GET /`** 或 **`GET /api`**(无鉴权)可得到 JSON 格式的简要说明(服务名、版本、`/api/docs` 与 `/api/health` 入口、路由前缀摘要)。 + +接入地址: +- 统一登录前端:`https://auth.shumengya.top` +- 后端 API:`https://auth.api.shumengya.top` +- 本地开发 API:`http://:8080` + +对外接入建议: +1. 第三方应用按钮跳转到统一登录前端。 +2. 登录成功后回跳到业务站点。 +3. 业务站点使用回跳带回的 `token` 调用后端 API。 + +示例按钮: +```html + + 使用萌芽统一账户认证登录 + +``` + +回跳说明: +- 用户已登录时,统一登录前端会提示“继续授权”或“切换账号”。 +- 登录成功后会回跳到 `redirect_uri`(或 `return_url`),并在 URL **`#fragment`**(哈希)中带上令牌与用户信息(见下表)。 +- 第三方应用拿到 `token` 后,建议调用 **`POST /api/auth/verify`**(无副作用、适合网关鉴权)或 **`GET /api/auth/me`**(会更新访问记录,适合业务拉全量资料)校验并解析用户身份。 + +### 统一登录前端:查询参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `redirect_uri` | 与 `return_url` 至少其一 | 登录成功后的回跳地址,须进行 URL 编码;可为绝对 URL 或相对路径(相对路径相对统一登录站点解析)。 | +| `return_url` | 同上 | 与 `redirect_uri` 同义,二者都传时优先 `redirect_uri`。 | +| `state` | 否 | OAuth 风格透传字符串;回跳时原样写入哈希参数,供业务防 CSRF 或关联会话。 | +| `prompt` | 否 | 预留;前端可读,当前可用于将来扩展交互策略。 | +| `client_id` | 否 | 第三方应用稳定标识(字母数字开头,可含 `_.:-`,最长 64)。写入用户「应用接入记录」,并随登录请求提交给后端。 | +| `client_name` | 否 | 展示用名称(最长 128),与 `client_id` 配对;可选。 | + +### 回跳 URL:`#` 哈希参数 + +成功授权后,前端将使用 [`URLSearchParams`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams) 写入哈希,例如:`https://app.example.com/auth/callback#token=...&expiresAt=...&account=...&username=...&state=...`。 + +| 参数 | 说明 | +|------|------| +| `token` | JWT,调用受保护接口时放在请求头 `Authorization: Bearer `。 | +| `expiresAt` | 过期时间,RFC3339(与签发侧一致,当前默认为登录时起算 **7 天**)。 | +| `account` | 账户名(与 JWT `sub` 一致)。 | +| `username` | 展示用昵称,可能为空。 | +| `state` | 若登录请求携带了 `state`,则原样返回。 | + +业务站点回调页应用脚本读取 `location.hash`,解析后**仅在 HTTPS 环境**将 `token` 存于内存或安全存储,并尽快用后端 **`POST /api/auth/verify`** 校验(勿仅信任哈希中的明文字段)。 + +### 第三方后端接入建议 + +1. **仅信服务端**:回调页将 `token` 交给自有后端,由后端请求 `POST https:///api/auth/verify`(JSON body:`{"token":"..."}`),根据 `valid` 与 `user.account` 建立会话。 +2. **CORS**:浏览器直连 API 时须后端已配置 CORS(本服务默认允许任意 `Origin`);若从服务端发起请求则不受 CORS 限制。 +3. **令牌过期**:`verify` / `me` 返回 401 或 `verify` 中 `valid:false` 时,应引导用户重新走统一登录。 + +## 认证与统一登录 + +### 登录获取统一令牌 +`POST /api/auth/login` + +请求: +```json +{ + "account": "demo", + "password": "demo123", + "clientId": "my-app", + "clientName": "我的应用" +} +``` + +`clientId` / `clientName` 可选;规则与请求头 `X-Auth-Client` / `X-Auth-Client-Name` 一致。传入且格式合法时,会在登录成功后写入该用户的 **应用接入记录**(见下文 `authClients`)。 + +响应: +```json +{ + "token": "jwt-token", + "expiresAt": "2026-03-14T12:00:00Z", + "user": { + "account": "demo", + "username": "示例用户", + "email": "demo@example.com", + "level": 0, + "sproutCoins": 10, + "secondaryEmails": ["demo2@example.com"], + "phone": "13800000000", + "avatarUrl": "https://example.com/avatar.png", + "websiteUrl": "https://example.com", + "bio": "### 简介", + "createdAt": "2026-03-14T12:00:00Z", + "updatedAt": "2026-03-14T12:00:00Z" + } +} +``` + +若账户已被管理员封禁,返回 **403**,且**不会签发 JWT**,响应示例: + +```json +{ + "error": "account is banned", + "banReason": "违规内容" +} +``` + +`banReason` 可能为空字符串或省略。 + +**常见 HTTP 状态码(登录)** + +| 状态码 | 含义 | +|--------|------| +| 200 | 成功,返回 `token`、`expiresAt`、`user`。 | +| 400 | 请求体非法或缺少 `account` / `password`。 | +| 401 | 账户不存在或密码错误(统一文案 `invalid credentials`)。 | +| 403 | 账户已封禁(见上文 JSON)。 | +| 500 | 服务器内部错误(读库、签发 JWT 失败等)。 | + +**JWT 概要**:算法 **HS256**;载荷含 `account`(与 `sub` 一致)、`iss`(见 `data/config/auth.json`)、`iat` / `exp`。客户端只需透传字符串,**勿在前端解析密钥**。 + +### 校验令牌 +`POST /api/auth/verify` + +请求: +```json +{ + "token": "jwt-token" +} +``` + +响应: +```json +{ + "valid": true, + "user": { "account": "demo", "...": "..." } +} +``` + +若账户已封禁,返回 **200** 且 `valid` 为 **false**(不返回 `user` 对象),示例: + +```json +{ + "valid": false, + "error": "account is banned", + "banReason": "违规内容" +} +``` + +令牌过期、签名错误、issuer 不匹配等解析失败时返回 **401**,示例:`{"valid": false, "error": "invalid token"}`。 + +`verify` 与 `me` 的取舍:**仅校验身份、不改变用户数据**时用 `verify`;需要最新资料、签到状态或写入「最后访问」时用 `GET /api/auth/me`(需 Bearer)。 + +**应用接入记录(可选)**:第三方在 **`POST /api/auth/verify`** 或 **`GET /api/auth/me`** 上携带请求头: + +- `X-Auth-Client`:应用 ID(格式同登录 JSON 的 `clientId`) +- `X-Auth-Client-Name`:可选展示名 + +校验成功且用户未封禁时,服务端会更新该用户 JSON 中的 `authClients` 数组(`clientId`、`displayName`、`firstSeenAt`、`lastSeenAt`)。**`POST /api/auth/verify` 的响应体 `user` 仍为 `Public()`,不含 `authClients`**,避免向调用方泄露用户在其他应用的接入情况;**`GET /api/auth/me`** 与管理员列表中的 `user`(`OwnerPublic`)**包含** `authClients`,用户可在统一登录前端的个人中心查看。 + +### 获取当前用户信息 +`GET /api/auth/me` + +请求头: +`Authorization: Bearer ` + +可选(由前端调用 `https://cf-ip-geo.smyhub.com/api` 等接口解析后传入,用于记录「最后访问 IP」与「最后显示位置」): +- `X-Visit-Ip`:客户端公网 IP(与地理接口返回的 `ip` 一致即可) +- `X-Visit-Location`:展示用位置文案(例如将 `geo.countryName`、`regionName`、`cityName` 拼接为 `中国 四川 成都`) + +**服务端回退(避免浏览器跨域导致头缺失)**:若未传 `X-Visit-Location`,后端会用 `X-Visit-Ip`;若也未传 `X-Visit-Ip`,则用连接的 `ClientIP()`(请在前置反向代理上正确传递 `X-Forwarded-For` 等,并在生产环境为 Gin 配置可信代理)。随后服务端请求 `GEO_LOOKUP_URL`(默认 `https://cf-ip-geo.smyhub.com/api?ip=`)解析展示位置并写入用户记录。 + +响应: +```json +{ + "user": { "account": "demo", "...": "..." }, + "checkIn": { + "rewardCoins": 1, + "checkedInToday": false, + "lastCheckInDate": "", + "lastCheckInAt": "", + "today": "2026-03-14" + } +} +``` + +> `user` 还会包含 `lastVisitAt`、`lastVisitDate`、`checkInDays`、`checkInStreak`、`visitDays`、`visitStreak` 等统计字段。 + +> 在登录用户本人、管理员列表等场景下,`user` 还可包含 `lastVisitIp`、`lastVisitDisplayLocation`(最近一次通过 `/api/auth/me` 上报的访问 IP 与位置文案)。**公开用户资料接口** `GET /api/public/users/:account` 与 **`POST /api/auth/verify` 的 `user` 中不包含这两项**(避免公开展示或第三方校验时令牌响应携带访问隐私)。 + +> 说明:密码不会返回。 + +若账户在登录后被封禁,持旧 JWT 调用 `GET /api/auth/me`、`PUT /api/auth/profile`、`POST /api/auth/check-in`、辅助邮箱等需登录接口时,返回 **403**,正文同登录封禁响应(`error` + 可选 `banReason`)。客户端应作废本地令牌。 + +### 每日签到 +`POST /api/auth/check-in` + +请求头: +`Authorization: Bearer ` + +响应: +```json +{ + "checkedIn": true, + "alreadyCheckedIn": false, + "rewardCoins": 1, + "awardedCoins": 1, + "message": "签到成功", + "user": { "account": "demo", "...": "..." } +} +``` + +### 更新当前用户资料 +`PUT /api/auth/profile` + +请求头: +`Authorization: Bearer ` + +请求(字段可选): +```json +{ + "password": "newpass", + "username": "新昵称", + "phone": "13800000000", + "avatarUrl": "https://example.com/avatar.png", + "websiteUrl": "https://example.com", + "bio": "### 新简介" +} +``` + +说明:`websiteUrl` 须为 `http`/`https` 地址;可传空字符串清除;未写协议时服务端会补全为 `https://`。 + +响应: +```json +{ + "user": { "account": "demo", "...": "..." } +} +``` + +## 用户广场 + +### 获取用户公开主页 +`GET /api/public/users/{account}` + +说明: +- 仅支持账户名 `account`,不支持昵称查询。 +- 适合第三方应用展示用户公开资料。 +- 若该账户已被封禁,返回 **404** `{"error":"user not found"}`(与不存在账户相同,避免公开资料泄露)。 +- 响应中含该用户**最近一次被服务端记录的**访问 IP(`lastVisitIp`)与展示用地理位置(`lastVisitDisplayLocation`,与本人中心一致);`POST /api/auth/verify` 返回的用户 JSON **不含**上述两项。 + +响应: +```json +{ + "user": { + "account": "demo", + "username": "示例用户", + "level": 3, + "sproutCoins": 10, + "avatarUrl": "https://example.com/avatar.png", + "websiteUrl": "https://example.com", + "lastVisitIp": "203.0.113.1", + "lastVisitDisplayLocation": "中国 广东省 深圳市", + "bio": "### 简介" + } +} +``` + +### 公开注册策略 +`GET /api/public/registration-policy` + +无需鉴权。用于前端判断是否展示「邀请码」输入框。 + +响应: +```json +{ + "requireInviteCode": false +} +``` + +当 `requireInviteCode` 为 **true** 时,`POST /api/auth/register` 必须携带有效 `inviteCode`(见下节)。 + +### 注册账号(发送邮箱验证码) +`POST /api/auth/register` + +请求: +```json +{ + "account": "demo", + "password": "demo123", + "username": "示例用户", + "email": "demo@example.com", + "inviteCode": "ABCD1234" +} +``` + +- `inviteCode`:可选。若服务端开启「强制邀请码」,则必填且须为管理员发放的未过期、未用尽邀请码。邀请码**不区分大小写**;成功完成 `verify-email` 创建用户后才会扣减使用次数。 + +响应: +```json +{ + "sent": true, + "expiresAt": "2026-03-14T12:10:00Z" +} +``` + +### 验证邮箱并完成注册 +`POST /api/auth/verify-email` + +请求: +```json +{ + "account": "demo", + "code": "123456" +} +``` + +响应: +```json +{ + "created": true, + "user": { "account": "demo", "...": "..." } +} +``` + +### 忘记密码(发送重置验证码) +`POST /api/auth/forgot-password` + +请求: +```json +{ + "account": "demo", + "email": "demo@example.com" +} +``` + +响应: +```json +{ + "sent": true, + "expiresAt": "2026-03-14T12:10:00Z" +} +``` + +### 重置密码 +`POST /api/auth/reset-password` + +请求: +```json +{ + "account": "demo", + "code": "123456", + "newPassword": "newpass" +} +``` + +响应: +```json +{ "reset": true } +``` + +### 申请添加辅助邮箱(发送验证码) +`POST /api/auth/secondary-email/request` + +请求头: +`Authorization: Bearer ` + +请求: +```json +{ + "email": "demo2@example.com" +} +``` + +响应: +```json +{ + "sent": true, + "expiresAt": "2026-03-14T12:10:00Z" +} +``` + +### 验证辅助邮箱 +`POST /api/auth/secondary-email/verify` + +请求头: +`Authorization: Bearer ` + +请求: +```json +{ + "email": "demo2@example.com", + "code": "123456" +} +``` + +响应: +```json +{ + "verified": true, + "user": { "account": "demo", "...": "..." } +} +``` + +## 管理端接口(需要管理员 Token) + +管理员 Token 存放在 `data/config/admin.json` 中;如果文件不存在,后端启动时会自动生成并写入该文件。 +请求时可使用以下任一方式携带: +- Query:`?token=` +- Header:`X-Admin-Token: ` + +### 签到奖励设置 +`GET /api/admin/check-in/config` + +`PUT /api/admin/check-in/config` + +请求: +```json +{ + "rewardCoins": 1 +} +``` +- Header:`Authorization: Bearer ` + +### 注册策略与邀请码 + +`GET /api/admin/registration` + +响应含 `requireInviteCode` 与 `invites` 数组(每项含 `code`、`note`、`maxUses`、`uses`、`expiresAt`、`createdAt`)。`maxUses` 为 0 表示不限次数。 + +`PUT /api/admin/registration` + +请求: +```json +{ "requireInviteCode": true } +``` + +`POST /api/admin/registration/invites` + +请求: +```json +{ + "note": "内测批次", + "maxUses": 10, + "expiresAt": "2026-12-31T15:59:59Z" +} +``` + +`expiresAt` 可省略;须为 RFC3339。响应 `201`,`invite` 内含服务端生成的 8 位邀请码。 + +`DELETE /api/admin/registration/invites/{code}` + +删除指定邀请码(`code` 与存储大小写可能不同,按不区分大小写匹配)。 + +### 获取用户列表 +`GET /api/admin/users` + +响应: +```json +{ + "total": 1, + "users": [{ "account": "demo", "...": "..." }] +} +``` + +### 新建用户 +`POST /api/admin/users` + +请求: +```json +{ + "account": "demo", + "password": "demo123", + "username": "示例用户", + "email": "demo@example.com", + "level": 0, + "sproutCoins": 10, + "secondaryEmails": ["demo2@example.com"], + "phone": "13800000000", + "avatarUrl": "https://example.com/avatar.png", + "websiteUrl": "https://example.com", + "bio": "### 简介" +} +``` + +### 更新用户 +`PUT /api/admin/users/{account}` + +请求(字段可选): +```json +{ + "password": "newpass", + "username": "新昵称", + "level": 1, + "secondaryEmails": ["demo2@example.com"], + "sproutCoins": 99, + "websiteUrl": "https://example.com", + "banned": true, + "banReason": "违规说明(最多 500 字)" +} +``` + +- `banned`:是否封禁;解封时请传 `false`,并可将 `banReason` 置为空字符串。 +- `banReason`:仅当用户处于封禁状态时允许设为非空;封禁时若首次写入会记录 `bannedAt`(RFC3339,存于用户 JSON)。 + +管理员列表 `GET /api/admin/users` 中每条 `user` 可含 `banned`、`banReason`(不含 `bannedAt` 亦可从存储文件中查看)。 + +### 删除用户 +`DELETE /api/admin/users/{account}` + +响应: +```json +{ "deleted": true } +``` + +## 数据存储说明 + +- 用户数据:`data/users/*.json` +- 注册待验证:`data/pending/*.json` +- 密码重置记录:`data/reset/*.json` +- 辅助邮箱验证:`data/secondary/*.json` +- 管理员 Token:`data/config/admin.json` +- JWT 配置:`data/config/auth.json` +- 邮件配置:`data/config/email.json` +- 注册策略与邀请码:`data/config/registration.json` + +## 快速联调用示例 + +```bash +# 服务根路径 JSON 说明 +curl -s http://localhost:8080/ | jq . + +# 登录 +curl -X POST http://localhost:8080/api/auth/login \ + -H 'Content-Type: application/json' \ + -d '{"account":"demo","password":"demo123"}' + +# 校验令牌(推荐第三方网关先调此接口) +curl -X POST http://localhost:8080/api/auth/verify \ + -H 'Content-Type: application/json' \ + -d '{"token":""}' + +# 使用令牌获取用户信息(会更新访问记录) +curl http://localhost:8080/api/auth/me \ + -H 'Authorization: Bearer ' +``` diff --git a/mengyastore-backend/README.md b/mengyastore-backend/README.md new file mode 100644 index 0000000..f2d615f --- /dev/null +++ b/mengyastore-backend/README.md @@ -0,0 +1,256 @@ +# 萌芽小店 · 后端 + +基于 **Go + Gin + GORM** 构建的 RESTful API 服务,负责商品管理、订单处理、用户认证、聊天消息等核心业务。 + +## 技术依赖 + +| 包 | 版本 | 用途 | +|----|------|------| +| gin | v1.9 | HTTP 路由框架 | +| gorm | v1.31 | ORM | +| gorm/driver/mysql | v1.6 | MySQL 驱动 | +| go-sql-driver/mysql | v1.9 | 底层 MySQL 连接 | +| gin-contrib/cors | latest | CORS 中间件 | +| google/uuid | latest | UUID 生成 | + +## 目录结构 + +``` +mengyastore-backend/ +├── main.go # 程序入口,路由注册 +├── cmd/ +│ └── migrate/ +│ └── main.go # 一次性 JSON→MySQL 数据迁移脚本 +├── data/ +│ └── json/ +│ └── settings.json # 服务配置(adminToken、DSN 等) +├── internal/ +│ ├── config/ +│ │ └── config.go # 配置加载 +│ ├── database/ +│ │ ├── db.go # GORM 初始化 + AutoMigrate +│ │ └── models.go # 数据库行结构体(GORM 模型) +│ ├── models/ +│ │ ├── product.go # 业务模型 Product +│ │ ├── order.go # 业务模型 Order +│ │ └── chat.go # 业务模型 ChatMessage +│ ├── storage/ +│ │ ├── jsonstore.go # 商品存储(GORM 实现) +│ │ ├── orderstore.go # 订单存储 +│ │ ├── sitestore.go # 站点设置存储 +│ │ ├── wishliststore.go # 收藏夹存储 +│ │ └── chatstore.go # 聊天消息存储(含内存级频率限制) +│ ├── handlers/ +│ │ ├── admin.go # AdminHandler 结构体 + requireAdmin +│ │ ├── admin_product.go # 商品 CRUD 接口 +│ │ ├── admin_site.go # 维护模式接口 +│ │ ├── admin_orders.go # 订单管理接口 +│ │ ├── admin_chat.go # 管理员聊天接口 +│ │ ├── public.go # 公开接口(商品列表、浏览量) +│ │ ├── order.go # 下单、确认订单接口 +│ │ ├── stats.go # 统计信息接口 +│ │ ├── wishlist.go # 收藏夹接口(用户) +│ │ └── chat.go # 聊天接口(用户) +│ └── auth/ +│ └── sproutgate.go # SproutGate OAuth 客户端 +``` + +## API 路由一览 + +### 公开接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/health` | 健康检查 | +| GET | `/api/products` | 获取商品列表(仅 active) | +| POST | `/api/products/:id/view` | 记录商品浏览量 | +| GET | `/api/stats` | 获取总订单数和总访问量 | +| POST | `/api/site/visit` | 记录站点访问 | +| GET | `/api/site/maintenance` | 获取维护状态 | +| POST | `/api/checkout` | 创建订单(生成支付二维码) | +| GET | `/api/orders` | 获取当前用户订单(需 Bearer token) | +| POST | `/api/orders/:id/confirm` | 确认付款(触发发货) | + +### 收藏夹(需登录) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/wishlist` | 获取收藏商品 ID 列表 | +| POST | `/api/wishlist` | 添加收藏 | +| DELETE | `/api/wishlist/:id` | 取消收藏 | + +### 聊天(需登录) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/chat/messages` | 获取自己的聊天记录 | +| POST | `/api/chat/messages` | 发送消息(1 秒频率限制) | + +### 管理员接口(需 `?token=xxx` 或 `Authorization: `) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/admin/token` | 获取令牌(用于验证) | +| GET | `/api/admin/products` | 获取全部商品(含卡密) | +| POST | `/api/admin/products` | 创建商品 | +| PUT | `/api/admin/products/:id` | 编辑商品 | +| PATCH | `/api/admin/products/:id/status` | 切换上下架 | +| DELETE | `/api/admin/products/:id` | 删除商品 | +| POST | `/api/admin/site/maintenance` | 设置维护模式 | +| GET | `/api/admin/orders` | 获取全部订单 | +| DELETE | `/api/admin/orders/:id` | 删除订单 | +| GET | `/api/admin/chat` | 获取全部用户对话 | +| GET | `/api/admin/chat/:account` | 获取指定用户对话 | +| POST | `/api/admin/chat/:account` | 管理员回复 | +| DELETE | `/api/admin/chat/:account` | 清除对话 | + +## 数据库表结构 + +### products + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | varchar(36) | UUID 主键 | +| name | varchar(255) | 商品名称 | +| price | double | 原价 | +| discount_price | double | 折扣价(0 = 无折扣)| +| tags | json | 标签数组 | +| cover_url | varchar(500) | 封面图 URL | +| screenshot_urls | json | 截图 URL 数组(最多 5 张)| +| verification_url | varchar(500) | 验证链接 | +| description | text | Markdown 描述 | +| active | tinyint(1) | 是否上架 | +| require_login | tinyint(1) | 是否必须登录购买 | +| max_per_account | bigint | 每账户最大购买数(0=不限)| +| total_sold | bigint | 累计销量 | +| view_count | bigint | 累计浏览量 | +| delivery_mode | varchar(20) | 发货模式:`auto` / `manual` | +| show_note | tinyint(1) | 下单时显示备注输入框 | +| show_contact | tinyint(1) | 下单时显示联系方式输入框 | +| created_at | datetime(3) | 创建时间 | + +### product_codes + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint unsigned | 自增主键 | +| product_id | varchar(36) | 关联商品 ID(索引)| +| code | text | 卡密内容 | + +### orders + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | varchar(36) | UUID 主键 | +| product_id | varchar(36) | 商品 ID(索引)| +| product_name | varchar(255) | 商品名称快照 | +| user_account | varchar(255) | 用户账号(可空,匿名)| +| user_name | varchar(255) | 用户昵称 | +| quantity | bigint | 购买数量 | +| delivered_codes | json | 已发放卡密 | +| status | varchar(20) | `pending` / `completed` | +| delivery_mode | varchar(20) | `auto` / `manual` | +| note | text | 用户备注 | +| contact_phone | varchar(50) | 联系手机号 | +| contact_email | varchar(255) | 联系邮箱 | +| created_at | datetime(3) | 下单时间 | + +### site_settings + +键值对存储,当前使用的键: + +| Key | 说明 | +|-----|------| +| `totalVisits` | 总访问量 | +| `maintenance` | 维护模式(`true` / `false`)| +| `maintenanceReason` | 维护原因文本 | + +### wishlists + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint unsigned | 自增主键 | +| account_id | varchar(255) | 用户账号(唯一索引)| +| product_id | varchar(36) | 商品 ID(联合唯一)| + +### chat_messages + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | varchar(36) | UUID 主键 | +| account_id | varchar(255) | 用户账号(索引)| +| account_name | varchar(255) | 用户昵称 | +| content | text | 消息内容 | +| sent_at | datetime(3) | 发送时间 | +| from_admin | tinyint(1) | 是否来自管理员 | + +## 配置文件 + +`data/json/settings.json`: + +```json +{ + "adminToken": "你的管理员令牌", + "authApiUrl": "https://auth.api.shumengya.top", + "databaseDsn": "" +} +``` + +`databaseDsn` 为空时自动使用测试数据库。也可以通过环境变量 `DATABASE_DSN` 覆盖。 + +## 发货逻辑 + +### 自动发货(`deliveryMode = "auto"`) + +1. `POST /api/checkout` → 从 `product_codes` 提取指定数量的卡密 +2. 商品 `quantity` 减少,卡密从数据库删除 +3. 卡密保存到订单 `delivered_codes` +4. 用户 `POST /api/orders/:id/confirm` 确认付款后,订单状态变为 `completed`,响应中返回卡密内容 +5. 同时调用 `IncrementSold` 增加销量统计 + +### 手动发货(`deliveryMode = "manual"`) + +1. `POST /api/checkout` → 创建订单,不提取卡密 +2. 用户 `POST /api/orders/:id/confirm` 后,订单变为 `completed`,但 `delivered_codes` 为空 +3. 管理员在后台查看订单的备注、手机号、邮箱后手动发货 + +## 本地开发 + +```bash +go run . # 启动服务(默认 :8080) +go build -o mengyastore-backend.exe . # 构建可执行文件 +go run ./cmd/migrate/main.go # 迁移旧 JSON 数据到数据库 +``` + +### 切换数据库 + +```bash +# 测试库(默认) +# host: 10.1.1.100:3306 / db: mengyastore-test + +# 生产库 +set DATABASE_DSN=mengyastore:mengyastore@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local +./mengyastore-backend.exe +``` + +## 认证说明 + +### 用户认证 + +通过 SproutGate OAuth 服务验证 Bearer Token: + +```go +result, err := authClient.VerifyToken(token) +// result.Valid, result.User.Account, result.User.Username +``` + +### 管理员认证 + +管理员令牌通过查询参数或 Authorization 头传入: + +``` +GET /api/admin/products?token=xxx +Authorization: xxx +``` + +令牌与 `settings.json` 中的 `adminToken` 比对。 diff --git a/mengyastore-backend/cmd/migrate/main.go b/mengyastore-backend/cmd/migrate/main.go new file mode 100644 index 0000000..5c203e6 --- /dev/null +++ b/mengyastore-backend/cmd/migrate/main.go @@ -0,0 +1,304 @@ +// migrate imports existing JSON data files into the MySQL database. +// Run once after switching to DB storage: +// +// go run ./cmd/migrate/main.go +package main + +import ( + "encoding/json" + "log" + "os" + "strconv" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/logger" + + "mengyastore-backend/internal/config" + "mengyastore-backend/internal/database" +) + +func main() { + cfg, err := config.Load("data/json/settings.json") + if err != nil { + log.Fatalf("load config: %v", err) + } + + db, err := gorm.Open(mysql.Open(cfg.DatabaseDSN), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + if err != nil { + log.Fatalf("open db: %v", err) + } + + // Ensure tables exist + if err := db.AutoMigrate( + &database.ProductRow{}, + &database.ProductCodeRow{}, + &database.OrderRow{}, + &database.SiteSettingRow{}, + &database.WishlistRow{}, + &database.ChatMessageRow{}, + ); err != nil { + log.Fatalf("auto migrate: %v", err) + } + + log.Println("数据库连接成功,开始导入...") + migrateProducts(db) + migrateOrders(db) + migrateWishlists(db) + migrateChats(db) + migrateSite(db) + log.Println("✅ 数据导入完成!") +} + +// ─── Products ───────────────────────────────────────────────────────────────── + +type jsonProduct struct { + ID string `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` + DiscountPrice float64 `json:"discountPrice"` + Tags []string `json:"tags"` + CoverURL string `json:"coverUrl"` + ScreenshotURLs []string `json:"screenshotUrls"` + Description string `json:"description"` + Active bool `json:"active"` + RequireLogin bool `json:"requireLogin"` + MaxPerAccount int `json:"maxPerAccount"` + TotalSold int `json:"totalSold"` + ViewCount int `json:"viewCount"` + DeliveryMode string `json:"deliveryMode"` + ShowNote bool `json:"showNote"` + ShowContact bool `json:"showContact"` + Codes []string `json:"codes"` + CreatedAt time.Time `json:"createdAt"` +} + +func migrateProducts(db *gorm.DB) { + data, err := os.ReadFile("data/json/products.json") + if err != nil { + log.Printf("[products] 文件不存在,跳过: %v", err) + return + } + var products []jsonProduct + if err := json.Unmarshal(data, &products); err != nil { + log.Printf("[products] JSON 解析失败: %v", err) + return + } + for _, p := range products { + if p.ID == "" { + continue + } + if p.DeliveryMode == "" { + p.DeliveryMode = "auto" + } + row := database.ProductRow{ + ID: p.ID, + Name: p.Name, + Price: p.Price, + DiscountPrice: p.DiscountPrice, + Tags: database.StringSlice(p.Tags), + CoverURL: p.CoverURL, + ScreenshotURLs: database.StringSlice(p.ScreenshotURLs), + Description: p.Description, + Active: p.Active, + RequireLogin: p.RequireLogin, + MaxPerAccount: p.MaxPerAccount, + TotalSold: p.TotalSold, + ViewCount: p.ViewCount, + DeliveryMode: p.DeliveryMode, + ShowNote: p.ShowNote, + ShowContact: p.ShowContact, + CreatedAt: p.CreatedAt, + } + if row.CreatedAt.IsZero() { + row.CreatedAt = time.Now() + } + result := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row) + if result.Error != nil { + log.Printf("[products] 导入 %s 失败: %v", p.ID, result.Error) + continue + } + // Codes → product_codes + for _, code := range p.Codes { + if code == "" { + continue + } + db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.ProductCodeRow{ + ProductID: p.ID, + Code: code, + }) + } + } + log.Printf("[products] 导入 %d 条商品", len(products)) +} + +// ─── Orders ─────────────────────────────────────────────────────────────────── + +type jsonOrder struct { + ID string `json:"id"` + ProductID string `json:"productId"` + ProductName string `json:"productName"` + UserAccount string `json:"userAccount"` + UserName string `json:"userName"` + Quantity int `json:"quantity"` + DeliveredCodes []string `json:"deliveredCodes"` + Status string `json:"status"` + DeliveryMode string `json:"deliveryMode"` + Note string `json:"note"` + ContactPhone string `json:"contactPhone"` + ContactEmail string `json:"contactEmail"` + CreatedAt time.Time `json:"createdAt"` +} + +func migrateOrders(db *gorm.DB) { + data, err := os.ReadFile("data/json/orders.json") + if err != nil { + log.Printf("[orders] 文件不存在,跳过: %v", err) + return + } + var orders []jsonOrder + if err := json.Unmarshal(data, &orders); err != nil { + log.Printf("[orders] JSON 解析失败: %v", err) + return + } + for _, o := range orders { + if o.ID == "" { + continue + } + if o.DeliveryMode == "" { + o.DeliveryMode = "auto" + } + if o.DeliveredCodes == nil { + o.DeliveredCodes = []string{} + } + row := database.OrderRow{ + ID: o.ID, + ProductID: o.ProductID, + ProductName: o.ProductName, + UserAccount: o.UserAccount, + UserName: o.UserName, + Quantity: o.Quantity, + DeliveredCodes: database.StringSlice(o.DeliveredCodes), + Status: o.Status, + DeliveryMode: o.DeliveryMode, + Note: o.Note, + ContactPhone: o.ContactPhone, + ContactEmail: o.ContactEmail, + CreatedAt: o.CreatedAt, + } + if row.CreatedAt.IsZero() { + row.CreatedAt = time.Now() + } + if result := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row); result.Error != nil { + log.Printf("[orders] 导入 %s 失败: %v", o.ID, result.Error) + } + } + log.Printf("[orders] 导入 %d 条订单", len(orders)) +} + +// ─── Wishlists ──────────────────────────────────────────────────────────────── + +func migrateWishlists(db *gorm.DB) { + data, err := os.ReadFile("data/json/wishlists.json") + if err != nil { + log.Printf("[wishlists] 文件不存在,跳过: %v", err) + return + } + var wl map[string][]string + if err := json.Unmarshal(data, &wl); err != nil { + log.Printf("[wishlists] JSON 解析失败: %v", err) + return + } + count := 0 + for account, productIDs := range wl { + for _, pid := range productIDs { + db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.WishlistRow{ + AccountID: account, + ProductID: pid, + }) + count++ + } + } + log.Printf("[wishlists] 导入 %d 条收藏记录", count) +} + +// ─── Chats ──────────────────────────────────────────────────────────────────── + +type jsonChatMsg struct { + ID string `json:"id"` + AccountID string `json:"accountId"` + AccountName string `json:"accountName"` + Content string `json:"content"` + SentAt time.Time `json:"sentAt"` + FromAdmin bool `json:"fromAdmin"` +} + +func migrateChats(db *gorm.DB) { + data, err := os.ReadFile("data/json/chats.json") + if err != nil { + log.Printf("[chats] 文件不存在,跳过: %v", err) + return + } + var convs map[string][]jsonChatMsg + if err := json.Unmarshal(data, &convs); err != nil { + log.Printf("[chats] JSON 解析失败: %v", err) + return + } + count := 0 + for _, msgs := range convs { + for _, m := range msgs { + if m.ID == "" { + continue + } + db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.ChatMessageRow{ + ID: m.ID, + AccountID: m.AccountID, + AccountName: m.AccountName, + Content: m.Content, + SentAt: m.SentAt, + FromAdmin: m.FromAdmin, + }) + count++ + } + } + log.Printf("[chats] 导入 %d 条聊天消息", count) +} + +// ─── Site settings ──────────────────────────────────────────────────────────── + +type jsonSite struct { + TotalVisits int `json:"totalVisits"` + Maintenance bool `json:"maintenance"` + MaintenanceReason string `json:"maintenanceReason"` +} + +func migrateSite(db *gorm.DB) { + data, err := os.ReadFile("data/json/site.json") + if err != nil { + log.Printf("[site] 文件不存在,跳过: %v", err) + return + } + var site jsonSite + if err := json.Unmarshal(data, &site); err != nil { + log.Printf("[site] JSON 解析失败: %v", err) + return + } + upsert := func(key, value string) { + db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "key"}}, + DoUpdates: clause.AssignmentColumns([]string{"value"}), + }).Create(&database.SiteSettingRow{Key: key, Value: value}) + } + upsert("totalVisits", strconv.Itoa(site.TotalVisits)) + maintenance := "false" + if site.Maintenance { + maintenance = "true" + } + upsert("maintenance", maintenance) + upsert("maintenanceReason", site.MaintenanceReason) + log.Printf("[site] 站点设置导入完成(访问量: %d)", site.TotalVisits) +} diff --git a/mengyastore-backend/data/json/orders.json b/mengyastore-backend/data/json/orders.json deleted file mode 100644 index 8abe671..0000000 --- a/mengyastore-backend/data/json/orders.json +++ /dev/null @@ -1,119 +0,0 @@ -[ - { - "id": "0bea9606-51aa-4fe2-a932-ab0e36ee33ca", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "LINUX-INVITE-001" - ], - "status": "pending", - "createdAt": "2026-03-19T17:23:46.1743551+08:00" - }, - { - "id": "5be3ecbd-873b-4ea2-9209-e96f6eb528cd", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "LINUX-INVITE-002" - ], - "status": "pending", - "createdAt": "2026-03-19T17:24:07.6045189+08:00" - }, - { - "id": "c0cbb6c7-76be-49ef-9e67-8d2ae890e555", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "啊伟大伟大伟大我" - ], - "status": "pending", - "createdAt": "2026-03-19T22:28:28.5393405+08:00" - }, - { - "id": "f299bbb4-0de4-4824-84ab-d1ccfb3b35dd", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "啊伟大伟大伟大伟大" - ], - "status": "pending", - "createdAt": "2026-03-20T10:32:38.352837+08:00" - }, - { - "id": "413931af-2867-4855-89af-515747d4b5e5", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "你是傻逼哈哈哈被骗了吧" - ], - "status": "pending", - "createdAt": "2026-03-20T10:32:55.2785291+08:00" - }, - { - "id": "59ab54e0-8b98-48d3-bf63-a843ef2c95a4", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "唐" - ], - "status": "pending", - "createdAt": "2026-03-20T10:39:37.9977301+08:00" - }, - { - "id": "94e82c71-8237-429f-b593-2530314b72af", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "", - "userName": "", - "quantity": 1, - "deliveredCodes": [ - "原神牛逼" - ], - "status": "completed", - "createdAt": "2026-03-20T10:40:45.3820749+08:00" - }, - { - "id": "058cad17-608c-4108-b012-af42f688a047", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "shumengya", - "userName": "树萌芽", - "quantity": 1, - "deliveredCodes": [ - "123123123131" - ], - "status": "completed", - "createdAt": "2026-03-20T10:44:21.375082+08:00" - }, - { - "id": "e95f30ab-da4f-4dec-872c-3c9047cd8193", - "productId": "seed-1", - "productName": "Linux Do 邀请码", - "userAccount": "shumengya", - "userName": "树萌芽", - "quantity": 1, - "deliveredCodes": [ - "131231231231231" - ], - "status": "completed", - "createdAt": "2026-03-20T10:57:13.3436565+08:00" - } -] \ No newline at end of file diff --git a/mengyastore-backend/data/json/products.json b/mengyastore-backend/data/json/products.json deleted file mode 100644 index d3a9363..0000000 --- a/mengyastore-backend/data/json/products.json +++ /dev/null @@ -1,181 +0,0 @@ -[ - { - "id": "seed-1", - "name": "Linux Do 邀请码", - "price": 7, - "discountPrice": 4, - "tags": [ - "邀请码", - "LinuxDo" - ], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 10, - "description": "Linux.do论坛邀请码 默认每天可以生成一个,先到先得.", - "active": true, - "createdAt": "2026-03-15T10:00:00+08:00", - "updatedAt": "2026-03-20T11:37:16.2219815+08:00" - }, - { - "id": "seed-2", - "name": "ChatGPT普号", - "price": 1, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 2, - "description": "ChatGPT 普号 纯手工注册 数量不多", - "active": true, - "createdAt": "2026-03-15T10:05:00+08:00", - "updatedAt": "2026-03-20T11:34:54.3522714+08:00" - }, - { - "id": "2b6b6051-bca7-42da-b127-c7b721c50c06", - "name": "谷歌账号", - "price": 20, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 1, - "description": "谷歌账号 现货 可绑定F2A验证", - "active": true, - "createdAt": "2026-03-15T20:52:52.0381722+08:00", - "updatedAt": "2026-03-19T19:33:05.6844325+08:00" - }, - { - "id": "b9922892-c197-44be-be87-637ccb6bebeb", - "name": "萌芽币", - "price": 999999, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 1, - "description": "非买品 仅展示", - "active": true, - "createdAt": "2026-03-15T21:03:00.0164528+08:00", - "updatedAt": "2026-03-19T19:33:07.508758+08:00" - }, - { - "id": "ee8e0140-221c-4bfa-b10a-13b1f98ea4e5", - "name": "Keep校园跑 代刷4公里", - "price": 1, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 1, - "description": "keep校园跑带刷 每天4-5公里 下单后直接联系我发账号", - "active": true, - "createdAt": "2026-03-15T21:06:11.9820102+08:00", - "updatedAt": "2026-03-19T19:33:09.1800225+08:00" - }, - { - "id": "00bbf5db-b99e-4e88-a8ee-e7747b5969fe", - "name": "学习通/慕课挂课脚本", - "price": 25, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 1, - "description": "学习通,慕课挂科脚本 手机 电脑都可以挂 不会弄可联系教你", - "active": true, - "createdAt": "2026-03-15T21:06:45.3807471+08:00", - "updatedAt": "2026-03-19T19:33:02.9673884+08:00" - }, - { - "id": "6c7bf494-ef2c-4221-9bf7-ec3c94070d25", - "name": "smyhub.com后缀域名邮箱", - "price": 5, - "discountPrice": 0, - "tags": [], - "quantity": 0, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [], - "viewCount": 1, - "description": "纪念意义,比如我自己的mail@smyhub.com 目前已经续费了5年到2031年", - "active": true, - "createdAt": "2026-03-18T22:17:41.3034538+08:00", - "updatedAt": "2026-03-19T19:32:26.7674929+08:00" - }, - { - "id": "a30a2275-1c9c-49e4-a402-3e446e3e0f5c", - "name": "萌芽账号邀请码", - "price": 10, - "discountPrice": 8, - "tags": [], - "quantity": 1, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [ - "原神牛逼" - ], - "viewCount": 0, - "description": "萌芽统一账号登录平台邀请码", - "active": true, - "createdAt": "2026-03-20T11:04:05.5787516+08:00", - "updatedAt": "2026-03-20T11:04:05.5787516+08:00" - }, - { - "id": "bcd5d73b-6ad9-4ed9-8e18-42ea0482ceb3", - "name": "Keep 代跑脚本", - "price": 50, - "discountPrice": 0, - "tags": [], - "quantity": 1, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [ - "傻逼" - ], - "viewCount": 0, - "description": "Keep 校园跑脚本", - "active": true, - "createdAt": "2026-03-20T11:17:36.1915376+08:00", - "updatedAt": "2026-03-20T11:17:36.1915376+08:00" - }, - { - "id": "7ab90d55-92c1-49d3-9d0a-01e5b1c08340", - "name": "原神牛逼", - "price": 0, - "discountPrice": 0, - "tags": [], - "quantity": 1, - "coverUrl": "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png", - "screenshotUrls": [], - "verificationUrl": "", - "codes": [ - "原神牛逼" - ], - "viewCount": 0, - "description": "购买后直接发送一句原神牛逼", - "active": true, - "createdAt": "2026-03-20T11:36:36.6726035+08:00", - "updatedAt": "2026-03-20T11:42:05.3303102+08:00" - } -] \ No newline at end of file diff --git a/mengyastore-backend/data/json/site.json b/mengyastore-backend/data/json/site.json deleted file mode 100644 index 05c3c4a..0000000 --- a/mengyastore-backend/data/json/site.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "totalVisits": 3 -} \ No newline at end of file diff --git a/mengyastore-backend/docker-compose.yml b/mengyastore-backend/docker-compose.yml index 6df578c..64c3ea8 100644 --- a/mengyastore-backend/docker-compose.yml +++ b/mengyastore-backend/docker-compose.yml @@ -8,6 +8,9 @@ services: environment: GIN_MODE: release TZ: Asia/Shanghai + # Production MySQL DSN — uses internal network address. + # Change to TestDSN or override via .env file for local testing. + DATABASE_DSN: "mengyastore:mengyastore@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local" volumes: - - ./data:/app/data + - ./config.json:/app/config.json:ro restart: unless-stopped diff --git a/mengyastore-backend/go.mod b/mengyastore-backend/go.mod index 331c63c..ee56f87 100644 --- a/mengyastore-backend/go.mod +++ b/mengyastore-backend/go.mod @@ -1,6 +1,6 @@ module mengyastore-backend -go 1.21 +go 1.21.0 require ( github.com/gin-contrib/cors v1.7.2 @@ -9,6 +9,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -18,7 +19,10 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/text v0.2.0 // indirect @@ -33,7 +37,9 @@ require ( golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.20.0 // indirect google.golang.org/protobuf v1.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/gorm v1.31.1 // indirect ) diff --git a/mengyastore-backend/go.sum b/mengyastore-backend/go.sum index 83f7fd1..c1afd06 100644 --- a/mengyastore-backend/go.sum +++ b/mengyastore-backend/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -26,6 +28,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -33,6 +37,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -87,6 +95,8 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= @@ -97,5 +107,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/mengyastore-backend/internal/auth/sproutgate.go b/mengyastore-backend/internal/auth/sproutgate.go index 124e671..6b672aa 100644 --- a/mengyastore-backend/internal/auth/sproutgate.go +++ b/mengyastore-backend/internal/auth/sproutgate.go @@ -26,6 +26,8 @@ type SproutGateUser struct { Account string `json:"account"` Username string `json:"username"` AvatarURL string `json:"avatarUrl"` + Level int `json:"level"` + Email string `json:"email"` } func NewSproutGateClient(apiURL string) *SproutGateClient { diff --git a/mengyastore-backend/internal/config/config.go b/mengyastore-backend/internal/config/config.go index fdc8266..c672df5 100644 --- a/mengyastore-backend/internal/config/config.go +++ b/mengyastore-backend/internal/config/config.go @@ -9,8 +9,18 @@ import ( type Config struct { AdminToken string `json:"adminToken"` AuthAPIURL string `json:"authApiUrl"` + + // Database DSN. If empty, falls back to the test DB DSN. + // Format: "user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local" + DatabaseDSN string `json:"databaseDsn"` } +// Default DSNs for each environment. +const ( + TestDSN = "mengyastore-test:mengyastore-test@tcp(10.1.1.100:3306)/mengyastore-test?charset=utf8mb4&parseTime=True&loc=Local" + ProdDSN = "mengyastore:mengyastore@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local" +) + func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { @@ -23,5 +33,12 @@ func Load(path string) (*Config, error) { if cfg.AdminToken == "" { cfg.AdminToken = "shumengya520" } + // Default to test DB if not configured; environment variable overrides config file. + if dsn := os.Getenv("DATABASE_DSN"); dsn != "" { + cfg.DatabaseDSN = dsn + } + if cfg.DatabaseDSN == "" { + cfg.DatabaseDSN = TestDSN + } return &cfg, nil } diff --git a/mengyastore-backend/internal/database/db.go b/mengyastore-backend/internal/database/db.go new file mode 100644 index 0000000..0ea56f9 --- /dev/null +++ b/mengyastore-backend/internal/database/db.go @@ -0,0 +1,45 @@ +package database + +import ( + "log" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// Open initialises a GORM DB connection and runs AutoMigrate for all models. +func Open(dsn string) (*gorm.DB, error) { + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Warn), + }) + if err != nil { + return nil, err + } + + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + sqlDB.SetMaxIdleConns(5) + sqlDB.SetMaxOpenConns(20) + sqlDB.SetConnMaxLifetime(time.Hour) + + if err := autoMigrate(db); err != nil { + return nil, err + } + log.Println("[DB] 数据库连接成功,表结构已同步") + return db, nil +} + +func autoMigrate(db *gorm.DB) error { + return db.AutoMigrate( + &ProductRow{}, + &ProductCodeRow{}, + &OrderRow{}, + &SiteSettingRow{}, + &WishlistRow{}, + &ChatMessageRow{}, + ) +} diff --git a/mengyastore-backend/internal/database/models.go b/mengyastore-backend/internal/database/models.go new file mode 100644 index 0000000..3f58378 --- /dev/null +++ b/mengyastore-backend/internal/database/models.go @@ -0,0 +1,121 @@ +package database + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "time" +) + +// StringSlice is a JSON-serialized string slice stored as a MySQL TEXT/JSON column. +type StringSlice []string + +func (s StringSlice) Value() (driver.Value, error) { + if s == nil { + return "[]", nil + } + b, err := json.Marshal(s) + return string(b), err +} + +func (s *StringSlice) Scan(src any) error { + var raw []byte + switch v := src.(type) { + case string: + raw = []byte(v) + case []byte: + raw = v + default: + return fmt.Errorf("StringSlice: unsupported type %T", src) + } + return json.Unmarshal(raw, s) +} + +// ─── Products ──────────────────────────────────────────────────────────────── + +// ProductRow is the GORM model for the `products` table. +type ProductRow struct { + ID string `gorm:"primaryKey;size:36"` + Name string `gorm:"size:255;not null"` + Price float64 `gorm:"not null;default:0"` + DiscountPrice float64 `gorm:"default:0"` + Tags StringSlice `gorm:"type:json"` + CoverURL string `gorm:"size:500"` + ScreenshotURLs StringSlice `gorm:"type:json"` + VerificationURL string `gorm:"size:500;default:''"` + Description string `gorm:"type:text"` + Active bool `gorm:"default:true;index"` + RequireLogin bool `gorm:"default:false"` + MaxPerAccount int `gorm:"default:0"` + TotalSold int `gorm:"default:0"` + ViewCount int `gorm:"default:0"` + DeliveryMode string `gorm:"size:20;default:'auto'"` + ShowNote bool `gorm:"default:false"` + ShowContact bool `gorm:"default:false"` + CreatedAt time.Time `gorm:"index"` +} + +func (ProductRow) TableName() string { return "products" } + +// ProductCodeRow stores individual codes for a product (one row per code). +type ProductCodeRow struct { + ID uint `gorm:"primaryKey;autoIncrement"` + ProductID string `gorm:"size:36;not null;index"` + Code string `gorm:"type:text;not null"` +} + +func (ProductCodeRow) TableName() string { return "product_codes" } + +// ─── Orders ────────────────────────────────────────────────────────────────── + +type OrderRow struct { + ID string `gorm:"primaryKey;size:36"` + ProductID string `gorm:"size:36;not null;index"` + ProductName string `gorm:"size:255;not null"` + UserAccount string `gorm:"size:255;index"` + UserName string `gorm:"size:255"` + Quantity int `gorm:"not null;default:1"` + DeliveredCodes StringSlice `gorm:"type:json"` + Status string `gorm:"size:20;not null;default:'pending';index"` + DeliveryMode string `gorm:"size:20;default:'auto'"` + Note string `gorm:"type:text"` + ContactPhone string `gorm:"size:50"` + ContactEmail string `gorm:"size:255"` + NotifyEmail string `gorm:"size:255"` + CreatedAt time.Time +} + +func (OrderRow) TableName() string { return "orders" } + +// ─── Site settings ─────────────────────────────────────────────────────────── + +// SiteSettingRow stores arbitrary key-value pairs for site-wide settings. +type SiteSettingRow struct { + Key string `gorm:"primaryKey;size:64"` + Value string `gorm:"type:text"` +} + +func (SiteSettingRow) TableName() string { return "site_settings" } + +// ─── Wishlists ─────────────────────────────────────────────────────────────── + +type WishlistRow struct { + ID uint `gorm:"primaryKey;autoIncrement"` + AccountID string `gorm:"size:255;not null;index:idx_wishlist,unique"` + ProductID string `gorm:"size:36;not null;index:idx_wishlist,unique"` +} + +func (WishlistRow) TableName() string { return "wishlists" } + +// ─── Chat messages ─────────────────────────────────────────────────────────── + +type ChatMessageRow struct { + ID string `gorm:"primaryKey;size:36"` + AccountID string `gorm:"size:255;not null;index"` + AccountName string `gorm:"size:255"` + Content string `gorm:"type:text;not null"` + SentAt time.Time `gorm:"not null"` + FromAdmin bool `gorm:"default:false"` +} + +func (ChatMessageRow) TableName() string { return "chat_messages" } diff --git a/mengyastore-backend/internal/email/email.go b/mengyastore-backend/internal/email/email.go new file mode 100644 index 0000000..b47eb9c --- /dev/null +++ b/mengyastore-backend/internal/email/email.go @@ -0,0 +1,192 @@ +package email + +import ( + "crypto/tls" + "fmt" + "net/smtp" + "strings" + "time" +) + +// Config holds SMTP sender configuration. +type Config struct { + SMTPHost string // e.g. smtp.qq.com + SMTPPort string // e.g. 465 (SSL) or 587 (STARTTLS) + From string // sender email address + Password string // SMTP auth password / app password + FromName string // display name, e.g. "萌芽小店" +} + +// IsConfigured returns true if enough config is present to send mail. +func (c *Config) IsConfigured() bool { + return c.From != "" && c.Password != "" && c.SMTPHost != "" +} + +// OrderNotifyData contains the data for an order notification email. +type OrderNotifyData struct { + ToEmail string + ToName string + ProductName string + OrderID string + Quantity int + Codes []string // empty for manual delivery + IsManual bool +} + +// SendOrderNotify sends an order delivery notification email. +// Returns nil if config is not ready or ToEmail is empty (silently skip). +func SendOrderNotify(cfg Config, data OrderNotifyData) error { + if !cfg.IsConfigured() || data.ToEmail == "" { + return nil + } + + if cfg.SMTPPort == "" { + cfg.SMTPPort = "465" + } + if cfg.SMTPHost == "" { + cfg.SMTPHost = "smtp.qq.com" + } + fromName := cfg.FromName + if fromName == "" { + fromName = "萌芽小店" + } + + subject := "【萌芽小店】您的订单已发货" + if data.IsManual { + subject = "【萌芽小店】您的订单正在处理中" + } + + body := buildBody(data) + + msg := buildMIMEMessage(cfg.From, fromName, data.ToEmail, subject, body) + + addr := fmt.Sprintf("%s:%s", cfg.SMTPHost, cfg.SMTPPort) + auth := smtp.PlainAuth("", cfg.From, cfg.Password, cfg.SMTPHost) + + // QQ mail uses SSL on port 465; use TLS dial directly. + if cfg.SMTPPort == "465" { + return sendSSL(addr, cfg.SMTPHost, auth, cfg.From, data.ToEmail, msg) + } + return smtp.SendMail(addr, auth, cfg.From, []string{data.ToEmail}, []byte(msg)) +} + +func buildBody(data OrderNotifyData) string { + var sb strings.Builder + now := time.Now().Format("2006 年 01 月 02 日 15:04:05") + + recipient := data.ToName + if recipient == "" { + recipient = "用户" + } + + sb.WriteString("尊敬的 ") + sb.WriteString(recipient) + sb.WriteString(",\n\n") + sb.WriteString(" 您好!感谢您在萌芽小店的支持与购买。\n\n") + + sb.WriteString("────────────────────────────────\n") + sb.WriteString(" 订单信息\n") + sb.WriteString("────────────────────────────────\n") + sb.WriteString(fmt.Sprintf(" 商品名称:%s\n", data.ProductName)) + sb.WriteString(fmt.Sprintf(" 订单编号:%s\n", data.OrderID)) + sb.WriteString(fmt.Sprintf(" 购买数量:%d 件\n", data.Quantity)) + sb.WriteString(fmt.Sprintf(" 通知时间:%s\n", now)) + sb.WriteString("────────────────────────────────\n\n") + + if data.IsManual { + sb.WriteString(" 您的订单已成功提交,目前正在等待人工审核与处理。\n") + sb.WriteString(" 工作人员将尽快为您安排发货,请耐心等候。\n") + sb.WriteString(" 发货完成后,我们将另行发送邮件通知。\n\n") + } else { + sb.WriteString(" 您的订单已完成自动发货,发货内容如下:\n\n") + if len(data.Codes) > 0 { + for i, code := range data.Codes { + sb.WriteString(fmt.Sprintf(" [%d] %s\n", i+1, code)) + } + sb.WriteString("\n") + } + sb.WriteString(" 请妥善保管以上发货内容,切勿泄露给他人。\n\n") + } + + sb.WriteString(" 如有任何疑问,请联系在线客服,我们将竭诚为您服务。\n\n") + sb.WriteString("────────────────────────────────\n") + sb.WriteString(" 此邮件由系统自动发送,请勿直接回复。\n") + sb.WriteString("────────────────────────────────\n") + return sb.String() +} + +func buildMIMEMessage(from, fromName, to, subject, body string) string { + encodedFromName := fmt.Sprintf("=?UTF-8?B?%s?=", encodeBase64(fromName)) + encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", encodeBase64(subject)) + return fmt.Sprintf( + "From: %s <%s>\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n%s", + encodedFromName, from, to, encodedSubject, encodeBase64(body), + ) +} + +func encodeBase64(s string) string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + b := []byte(s) + var buf strings.Builder + for i := 0; i < len(b); i += 3 { + remaining := len(b) - i + b0 := b[i] + b1 := byte(0) + b2 := byte(0) + if remaining > 1 { + b1 = b[i+1] + } + if remaining > 2 { + b2 = b[i+2] + } + buf.WriteByte(chars[b0>>2]) + buf.WriteByte(chars[((b0&0x03)<<4)|(b1>>4)]) + if remaining > 1 { + buf.WriteByte(chars[((b1&0x0f)<<2)|(b2>>6)]) + } else { + buf.WriteByte('=') + } + if remaining > 2 { + buf.WriteByte(chars[b2&0x3f]) + } else { + buf.WriteByte('=') + } + } + return buf.String() +} + +func sendSSL(addr, host string, auth smtp.Auth, from, to string, msg string) error { + tlsConfig := &tls.Config{ + ServerName: host, + MinVersion: tls.VersionTLS12, + } + conn, err := tls.Dial("tcp", addr, tlsConfig) + if err != nil { + return fmt.Errorf("tls dial: %w", err) + } + defer conn.Close() + + client, err := smtp.NewClient(conn, host) + if err != nil { + return fmt.Errorf("smtp new client: %w", err) + } + defer client.Quit() //nolint:errcheck + + if err = client.Auth(auth); err != nil { + return fmt.Errorf("smtp auth: %w", err) + } + if err = client.Mail(from); err != nil { + return fmt.Errorf("smtp MAIL FROM: %w", err) + } + if err = client.Rcpt(to); err != nil { + return fmt.Errorf("smtp RCPT TO: %w", err) + } + w, err := client.Data() + if err != nil { + return fmt.Errorf("smtp DATA: %w", err) + } + if _, err = fmt.Fprint(w, msg); err != nil { + return fmt.Errorf("smtp write body: %w", err) + } + return w.Close() +} diff --git a/mengyastore-backend/internal/handlers/admin.go b/mengyastore-backend/internal/handlers/admin.go index c704cf1..a159cee 100644 --- a/mengyastore-backend/internal/handlers/admin.go +++ b/mengyastore-backend/internal/handlers/admin.go @@ -1,160 +1,24 @@ package handlers import ( - "net/http" - "strings" - "github.com/gin-gonic/gin" + "net/http" "mengyastore-backend/internal/config" - "mengyastore-backend/internal/models" "mengyastore-backend/internal/storage" ) +// AdminHandler holds dependencies for all admin-related routes. type AdminHandler struct { - store *storage.JSONStore - cfg *config.Config + store *storage.JSONStore + cfg *config.Config + siteStore *storage.SiteStore + orderStore *storage.OrderStore + chatStore *storage.ChatStore } -type productPayload struct { - Name string `json:"name"` - Price float64 `json:"price"` - DiscountPrice float64 `json:"discountPrice"` - Tags string `json:"tags"` - CoverURL string `json:"coverUrl"` - Codes []string `json:"codes"` - ScreenshotURLs []string `json:"screenshotUrls"` - Description string `json:"description"` - Active *bool `json:"active"` -} - -type togglePayload struct { - Active bool `json:"active"` -} - -func NewAdminHandler(store *storage.JSONStore, cfg *config.Config) *AdminHandler { - return &AdminHandler{store: store, cfg: cfg} -} - -func (h *AdminHandler) GetAdminToken(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"token": h.cfg.AdminToken}) -} - -func (h *AdminHandler) ListAllProducts(c *gin.Context) { - if !h.requireAdmin(c) { - return - } - items, err := h.store.ListAll() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"data": items}) -} - -func (h *AdminHandler) CreateProduct(c *gin.Context) { - if !h.requireAdmin(c) { - return - } - var payload productPayload - if err := c.ShouldBindJSON(&payload); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) - return - } - screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) - if !valid { - c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) - return - } - active := true - if payload.Active != nil { - active = *payload.Active - } - product := models.Product{ - Name: payload.Name, - Price: payload.Price, - DiscountPrice: payload.DiscountPrice, - Tags: normalizeTags(payload.Tags), - CoverURL: strings.TrimSpace(payload.CoverURL), - Codes: payload.Codes, - ScreenshotURLs: screenshotURLs, - Description: payload.Description, - Active: active, - } - created, err := h.store.Create(product) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"data": created}) -} - -func (h *AdminHandler) UpdateProduct(c *gin.Context) { - if !h.requireAdmin(c) { - return - } - id := c.Param("id") - var payload productPayload - if err := c.ShouldBindJSON(&payload); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) - return - } - screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) - if !valid { - c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) - return - } - active := false - if payload.Active != nil { - active = *payload.Active - } - patch := models.Product{ - Name: payload.Name, - Price: payload.Price, - DiscountPrice: payload.DiscountPrice, - Tags: normalizeTags(payload.Tags), - CoverURL: strings.TrimSpace(payload.CoverURL), - Codes: payload.Codes, - ScreenshotURLs: screenshotURLs, - Description: payload.Description, - Active: active, - } - updated, err := h.store.Update(id, patch) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"data": updated}) -} - -func (h *AdminHandler) ToggleProduct(c *gin.Context) { - if !h.requireAdmin(c) { - return - } - id := c.Param("id") - var payload togglePayload - if err := c.ShouldBindJSON(&payload); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) - return - } - updated, err := h.store.Toggle(id, payload.Active) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"data": updated}) -} - -func (h *AdminHandler) DeleteProduct(c *gin.Context) { - if !h.requireAdmin(c) { - return - } - id := c.Param("id") - if err := h.store.Delete(id); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"status": "deleted"}) +func NewAdminHandler(store *storage.JSONStore, cfg *config.Config, siteStore *storage.SiteStore, orderStore *storage.OrderStore, chatStore *storage.ChatStore) *AdminHandler { + return &AdminHandler{store: store, cfg: cfg, siteStore: siteStore, orderStore: orderStore, chatStore: chatStore} } func (h *AdminHandler) requireAdmin(c *gin.Context) bool { @@ -168,43 +32,3 @@ func (h *AdminHandler) requireAdmin(c *gin.Context) bool { c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) return false } - -func normalizeScreenshotURLs(urls []string) ([]string, bool) { - cleaned := make([]string, 0, len(urls)) - for _, url := range urls { - trimmed := strings.TrimSpace(url) - if trimmed == "" { - continue - } - cleaned = append(cleaned, trimmed) - if len(cleaned) > 5 { - return nil, false - } - } - return cleaned, true -} - -func normalizeTags(tagsCSV string) []string { - if tagsCSV == "" { - return []string{} - } - parts := strings.Split(tagsCSV, ",") - clean := make([]string, 0, len(parts)) - seen := map[string]bool{} - for _, p := range parts { - t := strings.TrimSpace(p) - if t == "" { - continue - } - key := strings.ToLower(t) - if seen[key] { - continue - } - seen[key] = true - clean = append(clean, t) - if len(clean) >= 20 { - break - } - } - return clean -} diff --git a/mengyastore-backend/internal/handlers/admin_chat.go b/mengyastore-backend/internal/handlers/admin_chat.go new file mode 100644 index 0000000..098841a --- /dev/null +++ b/mengyastore-backend/internal/handlers/admin_chat.go @@ -0,0 +1,88 @@ +package handlers + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +// GetAllConversations returns all conversations (map of accountID -> messages). +func (h *AdminHandler) GetAllConversations(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + convs, err := h.chatStore.ListConversations() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"conversations": convs}}) +} + +// GetConversation returns all messages for a specific account. +func (h *AdminHandler) GetConversation(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + accountID := c.Param("account") + if accountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing account"}) + return + } + msgs, err := h.chatStore.GetMessages(accountID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"messages": msgs}}) +} + +type adminChatPayload struct { + Content string `json:"content"` +} + +// AdminReply sends a reply from admin to a specific user. +func (h *AdminHandler) AdminReply(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + accountID := c.Param("account") + if accountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing account"}) + return + } + var payload adminChatPayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + content := strings.TrimSpace(payload.Content) + if content == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "消息不能为空"}) + return + } + msg, err := h.chatStore.SendAdminMessage(accountID, content) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"message": msg}}) +} + +// ClearConversation deletes all messages with a specific user. +func (h *AdminHandler) ClearConversation(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + accountID := c.Param("account") + if accountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing account"}) + return + } + if err := h.chatStore.ClearConversation(accountID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} diff --git a/mengyastore-backend/internal/handlers/admin_orders.go b/mengyastore-backend/internal/handlers/admin_orders.go new file mode 100644 index 0000000..da70fa9 --- /dev/null +++ b/mengyastore-backend/internal/handlers/admin_orders.go @@ -0,0 +1,35 @@ +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func (h *AdminHandler) ListAllOrders(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + orders, err := h.orderStore.ListAll() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": orders}) +} + +func (h *AdminHandler) DeleteOrder(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + orderID := c.Param("id") + if orderID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing order id"}) + return + } + if err := h.orderStore.Delete(orderID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} diff --git a/mengyastore-backend/internal/handlers/admin_product.go b/mengyastore-backend/internal/handlers/admin_product.go new file mode 100644 index 0000000..6e8e330 --- /dev/null +++ b/mengyastore-backend/internal/handlers/admin_product.go @@ -0,0 +1,202 @@ +package handlers + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + + "mengyastore-backend/internal/models" +) + +type productPayload struct { + Name string `json:"name"` + Price float64 `json:"price"` + DiscountPrice float64 `json:"discountPrice"` + Tags string `json:"tags"` + CoverURL string `json:"coverUrl"` + Codes []string `json:"codes"` + ScreenshotURLs []string `json:"screenshotUrls"` + Description string `json:"description"` + Active *bool `json:"active"` + RequireLogin bool `json:"requireLogin"` + MaxPerAccount int `json:"maxPerAccount"` + DeliveryMode string `json:"deliveryMode"` + ShowNote bool `json:"showNote"` + ShowContact bool `json:"showContact"` +} + +type togglePayload struct { + Active bool `json:"active"` +} + +func (h *AdminHandler) GetAdminToken(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"token": h.cfg.AdminToken}) +} + +func (h *AdminHandler) ListAllProducts(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + items, err := h.store.ListAll() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": items}) +} + +func (h *AdminHandler) CreateProduct(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + var payload productPayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) + if !valid { + c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) + return + } + active := true + if payload.Active != nil { + active = *payload.Active + } + product := models.Product{ + Name: payload.Name, + Price: payload.Price, + DiscountPrice: payload.DiscountPrice, + Tags: normalizeTags(payload.Tags), + CoverURL: strings.TrimSpace(payload.CoverURL), + Codes: payload.Codes, + ScreenshotURLs: screenshotURLs, + Description: payload.Description, + Active: active, + RequireLogin: payload.RequireLogin, + MaxPerAccount: payload.MaxPerAccount, + DeliveryMode: payload.DeliveryMode, + ShowNote: payload.ShowNote, + ShowContact: payload.ShowContact, + } + created, err := h.store.Create(product) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": created}) +} + +func (h *AdminHandler) UpdateProduct(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + id := c.Param("id") + var payload productPayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) + if !valid { + c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) + return + } + active := false + if payload.Active != nil { + active = *payload.Active + } + patch := models.Product{ + Name: payload.Name, + Price: payload.Price, + DiscountPrice: payload.DiscountPrice, + Tags: normalizeTags(payload.Tags), + CoverURL: strings.TrimSpace(payload.CoverURL), + Codes: payload.Codes, + ScreenshotURLs: screenshotURLs, + Description: payload.Description, + Active: active, + RequireLogin: payload.RequireLogin, + MaxPerAccount: payload.MaxPerAccount, + DeliveryMode: payload.DeliveryMode, + ShowNote: payload.ShowNote, + ShowContact: payload.ShowContact, + } + updated, err := h.store.Update(id, patch) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": updated}) +} + +func (h *AdminHandler) ToggleProduct(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + id := c.Param("id") + var payload togglePayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + updated, err := h.store.Toggle(id, payload.Active) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": updated}) +} + +func (h *AdminHandler) DeleteProduct(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + id := c.Param("id") + if err := h.store.Delete(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "deleted"}) +} + +func normalizeScreenshotURLs(urls []string) ([]string, bool) { + cleaned := make([]string, 0, len(urls)) + for _, url := range urls { + trimmed := strings.TrimSpace(url) + if trimmed == "" { + continue + } + cleaned = append(cleaned, trimmed) + if len(cleaned) > 5 { + return nil, false + } + } + return cleaned, true +} + +func normalizeTags(tagsCSV string) []string { + if tagsCSV == "" { + return []string{} + } + parts := strings.Split(tagsCSV, ",") + clean := make([]string, 0, len(parts)) + seen := map[string]bool{} + for _, p := range parts { + t := strings.TrimSpace(p) + if t == "" { + continue + } + key := strings.ToLower(t) + if seen[key] { + continue + } + seen[key] = true + clean = append(clean, t) + if len(clean) >= 20 { + break + } + } + return clean +} diff --git a/mengyastore-backend/internal/handlers/admin_site.go b/mengyastore-backend/internal/handlers/admin_site.go new file mode 100644 index 0000000..7564138 --- /dev/null +++ b/mengyastore-backend/internal/handlers/admin_site.go @@ -0,0 +1,73 @@ +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "mengyastore-backend/internal/storage" +) + +type maintenancePayload struct { + Maintenance bool `json:"maintenance"` + Reason string `json:"reason"` +} + +func (h *AdminHandler) SetMaintenance(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + var payload maintenancePayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + if err := h.siteStore.SetMaintenance(payload.Maintenance, payload.Reason); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": gin.H{ + "maintenance": payload.Maintenance, + "reason": payload.Reason, + }, + }) +} + +func (h *AdminHandler) GetSMTPConfig(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + cfg, err := h.siteStore.GetSMTPConfig() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + // Mask password in response + masked := cfg + if masked.Password != "" { + masked.Password = "••••••••" + } + c.JSON(http.StatusOK, gin.H{"data": masked}) +} + +func (h *AdminHandler) SetSMTPConfig(c *gin.Context) { + if !h.requireAdmin(c) { + return + } + var payload storage.SMTPConfig + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + // If password is the masked sentinel, preserve the existing one + if payload.Password == "••••••••" { + existing, _ := h.siteStore.GetSMTPConfig() + payload.Password = existing.Password + } + if err := h.siteStore.SetSMTPConfig(payload); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} diff --git a/mengyastore-backend/internal/handlers/chat.go b/mengyastore-backend/internal/handlers/chat.go new file mode 100644 index 0000000..a17e3d7 --- /dev/null +++ b/mengyastore-backend/internal/handlers/chat.go @@ -0,0 +1,82 @@ +package handlers + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + + "mengyastore-backend/internal/auth" + "mengyastore-backend/internal/storage" +) + +type ChatHandler struct { + chatStore *storage.ChatStore + authClient *auth.SproutGateClient +} + +func NewChatHandler(chatStore *storage.ChatStore, authClient *auth.SproutGateClient) *ChatHandler { + return &ChatHandler{chatStore: chatStore, authClient: authClient} +} + +func (h *ChatHandler) requireChatUser(c *gin.Context) (account, name string, ok bool) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { + c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) + return "", "", false + } + token := strings.TrimPrefix(authHeader, "Bearer ") + result, err := h.authClient.VerifyToken(token) + if err != nil || !result.Valid || result.User == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"}) + return "", "", false + } + return result.User.Account, result.User.Username, true +} + +// GetMyMessages returns all chat messages for the currently logged-in user. +func (h *ChatHandler) GetMyMessages(c *gin.Context) { + account, _, ok := h.requireChatUser(c) + if !ok { + return + } + msgs, err := h.chatStore.GetMessages(account) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"messages": msgs}}) +} + +type chatMessagePayload struct { + Content string `json:"content"` +} + +// SendMyMessage sends a message from the current user to admin. +func (h *ChatHandler) SendMyMessage(c *gin.Context) { + account, name, ok := h.requireChatUser(c) + if !ok { + return + } + var payload chatMessagePayload + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + content := strings.TrimSpace(payload.Content) + if content == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "消息不能为空"}) + return + } + + msg, rateLimited, err := h.chatStore.SendUserMessage(account, name, content) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if rateLimited { + c.JSON(http.StatusTooManyRequests, gin.H{"error": "发送太频繁,请稍候"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"message": msg}}) +} diff --git a/mengyastore-backend/internal/handlers/order.go b/mengyastore-backend/internal/handlers/order.go index 32e1d9e..1b99226 100644 --- a/mengyastore-backend/internal/handlers/order.go +++ b/mengyastore-backend/internal/handlers/order.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" "mengyastore-backend/internal/auth" + "mengyastore-backend/internal/email" "mengyastore-backend/internal/models" "mengyastore-backend/internal/storage" ) @@ -19,47 +20,70 @@ const qrSize = "320x320" type OrderHandler struct { productStore *storage.JSONStore orderStore *storage.OrderStore + siteStore *storage.SiteStore authClient *auth.SproutGateClient } type checkoutPayload struct { - ProductID string `json:"productId"` - Quantity int `json:"quantity"` + ProductID string `json:"productId"` + Quantity int `json:"quantity"` + Note string `json:"note"` + ContactPhone string `json:"contactPhone"` + ContactEmail string `json:"contactEmail"` + NotifyEmail string `json:"notifyEmail"` } -func NewOrderHandler(productStore *storage.JSONStore, orderStore *storage.OrderStore, authClient *auth.SproutGateClient) *OrderHandler { - return &OrderHandler{productStore: productStore, orderStore: orderStore, authClient: authClient} +func NewOrderHandler(productStore *storage.JSONStore, orderStore *storage.OrderStore, siteStore *storage.SiteStore, authClient *auth.SproutGateClient) *OrderHandler { + return &OrderHandler{productStore: productStore, orderStore: orderStore, siteStore: siteStore, authClient: authClient} } -func (h *OrderHandler) tryExtractUser(c *gin.Context) (string, string) { +func (h *OrderHandler) sendOrderNotify(toEmail, toName, productName, orderID string, qty int, codes []string, isManual bool) { + if toEmail == "" { + return + } + cfg, err := h.siteStore.GetSMTPConfig() + if err != nil || !cfg.IsConfiguredEmail() { + return + } + go func() { + emailCfg := email.Config{ + SMTPHost: cfg.Host, + SMTPPort: cfg.Port, + From: cfg.Email, + Password: cfg.Password, + FromName: cfg.FromName, + } + if err := email.SendOrderNotify(emailCfg, email.OrderNotifyData{ + ToEmail: toEmail, + ToName: toName, + ProductName: productName, + OrderID: orderID, + Quantity: qty, + Codes: codes, + IsManual: isManual, + }); err != nil { + log.Printf("[Email] 发送通知失败 order=%s to=%s: %v", orderID, toEmail, err) + } else { + log.Printf("[Email] 发送通知成功 order=%s to=%s", orderID, toEmail) + } + }() +} + +func (h *OrderHandler) tryExtractUserWithEmail(c *gin.Context) (account, username, userEmail string) { authHeader := c.GetHeader("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { - log.Println("[Order] 无 Authorization header,匿名下单") - return "", "" + return "", "", "" } userToken := strings.TrimPrefix(authHeader, "Bearer ") - log.Printf("[Order] 检测到用户 token,正在验证 (长度=%d)", len(userToken)) - result, err := h.authClient.VerifyToken(userToken) - if err != nil { - log.Printf("[Order] 验证 token 失败: %v", err) - return "", "" + if err != nil || !result.Valid || result.User == nil { + return "", "", "" } - if !result.Valid { - log.Println("[Order] token 验证返回 valid=false") - return "", "" - } - if result.User == nil { - log.Println("[Order] token 验证成功但 user 为空") - return "", "" - } - - log.Printf("[Order] 用户身份验证成功: account=%s username=%s", result.User.Account, result.User.Username) - return result.User.Account, result.User.Username + return result.User.Account, result.User.Username, result.User.Email } func (h *OrderHandler) CreateOrder(c *gin.Context) { - userAccount, userName := h.tryExtractUser(c) + userAccount, userName, userEmail := h.tryExtractUserWithEmail(c) var payload checkoutPayload if err := c.ShouldBindJSON(&payload); err != nil { @@ -85,21 +109,72 @@ func (h *OrderHandler) CreateOrder(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "product is not available"}) return } + + if product.RequireLogin && userAccount == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "该商品需要登录后才能购买"}) + return + } + + if product.MaxPerAccount > 0 && userAccount != "" { + purchased, err := h.orderStore.CountPurchasedByAccount(userAccount, product.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if purchased+payload.Quantity > product.MaxPerAccount { + remain := product.MaxPerAccount - purchased + if remain <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("每个账户最多购买 %d 个,您已达上限", product.MaxPerAccount)}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("每个账户最多购买 %d 个,您还可购买 %d 个", product.MaxPerAccount, remain)}) + } + return + } + } + if product.Quantity < payload.Quantity { c.JSON(http.StatusBadRequest, gin.H{"error": "库存不足"}) return } - deliveredCodes, ok := extractCodes(&product, payload.Quantity) - if !ok { - c.JSON(http.StatusBadRequest, gin.H{"error": "卡密不足"}) - return + isManual := product.DeliveryMode == "manual" + + var deliveredCodes []string + var updatedProduct models.Product + + if isManual { + updatedProduct = product + } else { + var ok bool + deliveredCodes, ok = extractCodes(&product, payload.Quantity) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "卡密不足"}) + return + } + product.Quantity = len(product.Codes) + updatedProduct, err = h.productStore.Update(product.ID, product) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } } - product.Quantity = len(product.Codes) - updatedProduct, err := h.productStore.Update(product.ID, product) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return + + deliveryMode := product.DeliveryMode + if deliveryMode == "" { + deliveryMode = "auto" + } + + // Notification email priority: + // 1. SproutGate account email (logged-in user, most reliable) + // 2. notifyEmail passed by frontend (also comes from authState.email) + // 3. contactEmail explicitly filled by user in checkout form + // 4. empty → skip sending + notifyEmail := strings.TrimSpace(userEmail) + if notifyEmail == "" { + notifyEmail = strings.TrimSpace(payload.NotifyEmail) + } + if notifyEmail == "" { + notifyEmail = strings.TrimSpace(payload.ContactEmail) } order := models.Order{ @@ -110,6 +185,11 @@ func (h *OrderHandler) CreateOrder(c *gin.Context) { Quantity: payload.Quantity, DeliveredCodes: deliveredCodes, Status: "pending", + DeliveryMode: deliveryMode, + Note: strings.TrimSpace(payload.Note), + ContactPhone: strings.TrimSpace(payload.ContactPhone), + ContactEmail: strings.TrimSpace(payload.ContactEmail), + NotifyEmail: notifyEmail, } created, err := h.orderStore.Create(order) if err != nil { @@ -117,6 +197,17 @@ func (h *OrderHandler) CreateOrder(c *gin.Context) { return } + if !isManual { + if err := h.productStore.IncrementSold(updatedProduct.ID, payload.Quantity); err != nil { + log.Printf("[Order] 更新销量失败 (非致命): %v", err) + } + // Send delivery notification for auto-delivery orders immediately + h.sendOrderNotify(notifyEmail, userName, updatedProduct.Name, created.ID, payload.Quantity, deliveredCodes, false) + } else { + // For manual delivery, notify user that order is received and pending + h.sendOrderNotify(notifyEmail, userName, updatedProduct.Name, created.ID, payload.Quantity, nil, true) + } + qrPayload := fmt.Sprintf("order:%s:%s", created.ID, created.ProductID) qrURL := fmt.Sprintf("https://api.qrserver.com/v1/create-qr-code/?size=%s&data=%s", qrSize, url.QueryEscape(qrPayload)) @@ -139,11 +230,25 @@ func (h *OrderHandler) ConfirmOrder(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } + + isManual := order.DeliveryMode == "manual" + + // For manual delivery, send a "delivered" notification when admin confirms + if isManual { + confirmNotifyEmail := order.NotifyEmail + if confirmNotifyEmail == "" { + confirmNotifyEmail = order.ContactEmail + } + h.sendOrderNotify(confirmNotifyEmail, order.UserName, order.ProductName, order.ID, order.Quantity, order.DeliveredCodes, false) + } + c.JSON(http.StatusOK, gin.H{ "data": gin.H{ "orderId": order.ID, "status": order.Status, + "deliveryMode": order.DeliveryMode, "deliveredCodes": order.DeliveredCodes, + "isManual": isManual, }, }) } diff --git a/mengyastore-backend/internal/handlers/stats.go b/mengyastore-backend/internal/handlers/stats.go index 97ba25c..2149749 100644 --- a/mengyastore-backend/internal/handlers/stats.go +++ b/mengyastore-backend/internal/handlers/stats.go @@ -50,3 +50,17 @@ func (h *StatsHandler) RecordVisit(c *gin.Context) { }, }) } + +func (h *StatsHandler) GetMaintenance(c *gin.Context) { + enabled, reason, err := h.siteStore.GetMaintenance() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": gin.H{ + "maintenance": enabled, + "reason": reason, + }, + }) +} diff --git a/mengyastore-backend/internal/handlers/wishlist.go b/mengyastore-backend/internal/handlers/wishlist.go new file mode 100644 index 0000000..8a2affe --- /dev/null +++ b/mengyastore-backend/internal/handlers/wishlist.go @@ -0,0 +1,88 @@ +package handlers + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + + "mengyastore-backend/internal/auth" + "mengyastore-backend/internal/storage" +) + +type WishlistHandler struct { + wishlistStore *storage.WishlistStore + authClient *auth.SproutGateClient +} + +func NewWishlistHandler(wishlistStore *storage.WishlistStore, authClient *auth.SproutGateClient) *WishlistHandler { + return &WishlistHandler{wishlistStore: wishlistStore, authClient: authClient} +} + +func (h *WishlistHandler) requireUser(c *gin.Context) (string, bool) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { + c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) + return "", false + } + token := strings.TrimPrefix(authHeader, "Bearer ") + result, err := h.authClient.VerifyToken(token) + if err != nil || !result.Valid || result.User == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"}) + return "", false + } + return result.User.Account, true +} + +func (h *WishlistHandler) GetWishlist(c *gin.Context) { + account, ok := h.requireUser(c) + if !ok { + return + } + ids, err := h.wishlistStore.Get(account) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"items": ids}}) +} + +type wishlistItemPayload struct { + ProductID string `json:"productId"` +} + +func (h *WishlistHandler) AddToWishlist(c *gin.Context) { + account, ok := h.requireUser(c) + if !ok { + return + } + var payload wishlistItemPayload + if err := c.ShouldBindJSON(&payload); err != nil || payload.ProductID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) + return + } + if err := h.wishlistStore.Add(account, payload.ProductID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + ids, _ := h.wishlistStore.Get(account) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"items": ids}}) +} + +func (h *WishlistHandler) RemoveFromWishlist(c *gin.Context) { + account, ok := h.requireUser(c) + if !ok { + return + } + productID := c.Param("id") + if productID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing product id"}) + return + } + if err := h.wishlistStore.Remove(account, productID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + ids, _ := h.wishlistStore.Get(account) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"items": ids}}) +} diff --git a/mengyastore-backend/internal/models/chat.go b/mengyastore-backend/internal/models/chat.go new file mode 100644 index 0000000..22f02e7 --- /dev/null +++ b/mengyastore-backend/internal/models/chat.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type ChatMessage struct { + ID string `json:"id"` + AccountID string `json:"accountId"` + AccountName string `json:"accountName"` + Content string `json:"content"` + SentAt time.Time `json:"sentAt"` + FromAdmin bool `json:"fromAdmin"` +} diff --git a/mengyastore-backend/internal/models/order.go b/mengyastore-backend/internal/models/order.go index 12b3a0e..256c57b 100644 --- a/mengyastore-backend/internal/models/order.go +++ b/mengyastore-backend/internal/models/order.go @@ -11,5 +11,10 @@ type Order struct { Quantity int `json:"quantity"` DeliveredCodes []string `json:"deliveredCodes"` Status string `json:"status"` + DeliveryMode string `json:"deliveryMode"` + Note string `json:"note"` + ContactPhone string `json:"contactPhone"` + ContactEmail string `json:"contactEmail"` + NotifyEmail string `json:"notifyEmail"` CreatedAt time.Time `json:"createdAt"` } diff --git a/mengyastore-backend/internal/models/product.go b/mengyastore-backend/internal/models/product.go index 6553f8b..3873922 100644 --- a/mengyastore-backend/internal/models/product.go +++ b/mengyastore-backend/internal/models/product.go @@ -16,6 +16,12 @@ type Product struct { ViewCount int `json:"viewCount"` Description string `json:"description"` Active bool `json:"active"` + RequireLogin bool `json:"requireLogin"` + MaxPerAccount int `json:"maxPerAccount"` + TotalSold int `json:"totalSold"` + DeliveryMode string `json:"deliveryMode"` + ShowNote bool `json:"showNote"` + ShowContact bool `json:"showContact"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } diff --git a/mengyastore-backend/internal/storage/chatstore.go b/mengyastore-backend/internal/storage/chatstore.go new file mode 100644 index 0000000..1625ceb --- /dev/null +++ b/mengyastore-backend/internal/storage/chatstore.go @@ -0,0 +1,99 @@ +package storage + +import ( + "sync" + "time" + + "github.com/google/uuid" + "gorm.io/gorm" + + "mengyastore-backend/internal/database" + "mengyastore-backend/internal/models" +) + +type ChatStore struct { + db *gorm.DB + mu sync.Mutex + lastSent map[string]time.Time +} + +func NewChatStore(db *gorm.DB) (*ChatStore, error) { + return &ChatStore{db: db, lastSent: make(map[string]time.Time)}, nil +} + +func chatRowToModel(row database.ChatMessageRow) models.ChatMessage { + return models.ChatMessage{ + ID: row.ID, + AccountID: row.AccountID, + AccountName: row.AccountName, + Content: row.Content, + SentAt: row.SentAt, + FromAdmin: row.FromAdmin, + } +} + +func (s *ChatStore) GetMessages(accountID string) ([]models.ChatMessage, error) { + var rows []database.ChatMessageRow + if err := s.db.Where("account_id = ?", accountID).Order("sent_at ASC").Find(&rows).Error; err != nil { + return nil, err + } + msgs := make([]models.ChatMessage, len(rows)) + for i, r := range rows { + msgs[i] = chatRowToModel(r) + } + return msgs, nil +} + +func (s *ChatStore) ListConversations() (map[string][]models.ChatMessage, error) { + var rows []database.ChatMessageRow + if err := s.db.Order("account_id, sent_at ASC").Find(&rows).Error; err != nil { + return nil, err + } + result := make(map[string][]models.ChatMessage) + for _, r := range rows { + result[r.AccountID] = append(result[r.AccountID], chatRowToModel(r)) + } + return result, nil +} + +func (s *ChatStore) SendUserMessage(accountID, accountName, content string) (models.ChatMessage, bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if last, ok := s.lastSent[accountID]; ok && time.Since(last) < time.Second { + return models.ChatMessage{}, true, nil + } + s.lastSent[accountID] = time.Now() + + row := database.ChatMessageRow{ + ID: uuid.New().String(), + AccountID: accountID, + AccountName: accountName, + Content: content, + SentAt: time.Now(), + FromAdmin: false, + } + if err := s.db.Create(&row).Error; err != nil { + return models.ChatMessage{}, false, err + } + return chatRowToModel(row), false, nil +} + +func (s *ChatStore) SendAdminMessage(accountID, content string) (models.ChatMessage, error) { + row := database.ChatMessageRow{ + ID: uuid.New().String(), + AccountID: accountID, + AccountName: "管理员", + Content: content, + SentAt: time.Now(), + FromAdmin: true, + } + if err := s.db.Create(&row).Error; err != nil { + return models.ChatMessage{}, err + } + return chatRowToModel(row), nil +} + +func (s *ChatStore) ClearConversation(accountID string) error { + return s.db.Where("account_id = ?", accountID).Delete(&database.ChatMessageRow{}).Error +} diff --git a/mengyastore-backend/internal/storage/jsonstore.go b/mengyastore-backend/internal/storage/jsonstore.go index 273c15b..8a146b5 100644 --- a/mengyastore-backend/internal/storage/jsonstore.go +++ b/mengyastore-backend/internal/storage/jsonstore.go @@ -2,16 +2,15 @@ package storage import ( "crypto/sha256" - "encoding/json" "fmt" - "os" - "path/filepath" "strings" "sync" "time" "github.com/google/uuid" + "gorm.io/gorm" + "mengyastore-backend/internal/database" "mengyastore-backend/internal/models" ) @@ -20,238 +19,235 @@ const viewCooldown = 6 * time.Hour const maxScreenshotURLs = 5 type JSONStore struct { - path string + db *gorm.DB mu sync.Mutex recentViews map[string]time.Time } -func NewJSONStore(path string) (*JSONStore, error) { - if err := ensureProductsFile(path); err != nil { - return nil, err - } +func NewJSONStore(db *gorm.DB) (*JSONStore, error) { return &JSONStore{ - path: path, + db: db, recentViews: make(map[string]time.Time), }, nil } -func ensureProductsFile(path string) error { - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("mkdir data dir: %w", err) - } - if _, err := os.Stat(path); err == nil { - return nil - } else if !os.IsNotExist(err) { - return fmt.Errorf("stat data file: %w", err) +// rowToModel converts a ProductRow (+ codes) to a models.Product. +func rowToModel(row database.ProductRow, codes []string) models.Product { + return models.Product{ + ID: row.ID, + Name: row.Name, + Price: row.Price, + DiscountPrice: row.DiscountPrice, + Tags: row.Tags, + CoverURL: row.CoverURL, + ScreenshotURLs: row.ScreenshotURLs, + VerificationURL: row.VerificationURL, + Description: row.Description, + Active: row.Active, + RequireLogin: row.RequireLogin, + MaxPerAccount: row.MaxPerAccount, + TotalSold: row.TotalSold, + ViewCount: row.ViewCount, + DeliveryMode: row.DeliveryMode, + ShowNote: row.ShowNote, + ShowContact: row.ShowContact, + Codes: codes, + Quantity: len(codes), + CreatedAt: row.CreatedAt, } +} - initial := []models.Product{} - bytes, err := json.MarshalIndent(initial, "", " ") - if err != nil { - return fmt.Errorf("init json: %w", err) +func (s *JSONStore) loadCodes(productID string) ([]string, error) { + var rows []database.ProductCodeRow + if err := s.db.Where("product_id = ?", productID).Find(&rows).Error; err != nil { + return nil, err } - if err := os.WriteFile(path, bytes, 0o644); err != nil { - return fmt.Errorf("write init json: %w", err) + codes := make([]string, len(rows)) + for i, r := range rows { + codes[i] = r.Code } - return nil + return codes, nil +} + +func (s *JSONStore) replaceCodes(productID string, codes []string) error { + if err := s.db.Where("product_id = ?", productID).Delete(&database.ProductCodeRow{}).Error; err != nil { + return err + } + if len(codes) == 0 { + return nil + } + rows := make([]database.ProductCodeRow, 0, len(codes)) + for _, code := range codes { + rows = append(rows, database.ProductCodeRow{ProductID: productID, Code: code}) + } + return s.db.CreateInBatches(rows, 100).Error } func (s *JSONStore) ListAll() ([]models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - return s.readAll() + var rows []database.ProductRow + if err := s.db.Order("created_at DESC").Find(&rows).Error; err != nil { + return nil, err + } + products := make([]models.Product, 0, len(rows)) + for _, row := range rows { + codes, _ := s.loadCodes(row.ID) + products = append(products, rowToModel(row, codes)) + } + return products, nil } func (s *JSONStore) ListActive() ([]models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { + var rows []database.ProductRow + if err := s.db.Where("active = ?", true).Order("created_at DESC").Find(&rows).Error; err != nil { return nil, err } - active := make([]models.Product, 0, len(items)) - for _, item := range items { - if item.Active { - active = append(active, item) - } + products := make([]models.Product, 0, len(rows)) + for _, row := range rows { + // For public listing we don't expose codes, but we still need Quantity + var count int64 + s.db.Model(&database.ProductCodeRow{}).Where("product_id = ?", row.ID).Count(&count) + row.Active = true + p := rowToModel(row, nil) + p.Quantity = int(count) + p.Codes = nil + products = append(products, p) } - return active, nil + return products, nil } func (s *JSONStore) GetByID(id string) (models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - - items, err := s.readAll() - if err != nil { - return models.Product{}, err + var row database.ProductRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Product{}, fmt.Errorf("product not found") } - for _, item := range items { - if item.ID == id { - return item, nil - } - } - return models.Product{}, fmt.Errorf("product not found") + codes, _ := s.loadCodes(id) + return rowToModel(row, codes), nil } func (s *JSONStore) Create(p models.Product) (models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { - return models.Product{}, err - } p = normalizeProduct(p) p.ID = uuid.NewString() now := time.Now() p.CreatedAt = now - p.UpdatedAt = now - items = append(items, p) - if err := s.writeAll(items); err != nil { + + row := database.ProductRow{ + ID: p.ID, + Name: p.Name, + Price: p.Price, + DiscountPrice: p.DiscountPrice, + Tags: database.StringSlice(p.Tags), + CoverURL: p.CoverURL, + ScreenshotURLs: database.StringSlice(p.ScreenshotURLs), + VerificationURL: p.VerificationURL, + Description: p.Description, + Active: p.Active, + RequireLogin: p.RequireLogin, + MaxPerAccount: p.MaxPerAccount, + TotalSold: p.TotalSold, + ViewCount: p.ViewCount, + DeliveryMode: p.DeliveryMode, + ShowNote: p.ShowNote, + ShowContact: p.ShowContact, + CreatedAt: now, + } + if err := s.db.Create(&row).Error; err != nil { return models.Product{}, err } + if err := s.replaceCodes(p.ID, p.Codes); err != nil { + return models.Product{}, err + } + p.Quantity = len(p.Codes) return p, nil } func (s *JSONStore) Update(id string, patch models.Product) (models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { + var row database.ProductRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Product{}, fmt.Errorf("product not found") + } + normalized := normalizeProduct(patch) + + if err := s.db.Model(&row).Updates(map[string]interface{}{ + "name": normalized.Name, + "price": normalized.Price, + "discount_price": normalized.DiscountPrice, + "tags": database.StringSlice(normalized.Tags), + "cover_url": normalized.CoverURL, + "screenshot_urls": database.StringSlice(normalized.ScreenshotURLs), + "verification_url": normalized.VerificationURL, + "description": normalized.Description, + "active": normalized.Active, + "require_login": normalized.RequireLogin, + "max_per_account": normalized.MaxPerAccount, + "delivery_mode": normalized.DeliveryMode, + "show_note": normalized.ShowNote, + "show_contact": normalized.ShowContact, + }).Error; err != nil { return models.Product{}, err } - for i, item := range items { - if item.ID == id { - normalized := normalizeProduct(patch) - item.Name = normalized.Name - item.Price = normalized.Price - item.DiscountPrice = normalized.DiscountPrice - item.Tags = normalized.Tags - item.CoverURL = normalized.CoverURL - item.ScreenshotURLs = normalized.ScreenshotURLs - item.VerificationURL = normalized.VerificationURL - item.Codes = normalized.Codes - item.Quantity = normalized.Quantity - item.Description = normalized.Description - item.Active = normalized.Active - item.UpdatedAt = time.Now() - items[i] = item - if err := s.writeAll(items); err != nil { - return models.Product{}, err - } - return item, nil - } + if err := s.replaceCodes(id, normalized.Codes); err != nil { + return models.Product{}, err } - return models.Product{}, fmt.Errorf("product not found") + + var updated database.ProductRow + s.db.First(&updated, "id = ?", id) + codes, _ := s.loadCodes(id) + return rowToModel(updated, codes), nil } func (s *JSONStore) Toggle(id string, active bool) (models.Product, error) { - s.mu.Lock() - defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { + if err := s.db.Model(&database.ProductRow{}).Where("id = ?", id).Update("active", active).Error; err != nil { return models.Product{}, err } - for i, item := range items { - if item.ID == id { - item.Active = active - item.UpdatedAt = time.Now() - items[i] = item - if err := s.writeAll(items); err != nil { - return models.Product{}, err - } - return item, nil - } + var row database.ProductRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Product{}, fmt.Errorf("product not found") } - return models.Product{}, fmt.Errorf("product not found") + codes, _ := s.loadCodes(id) + return rowToModel(row, codes), nil +} + +func (s *JSONStore) IncrementSold(id string, count int) error { + return s.db.Model(&database.ProductRow{}).Where("id = ?", id). + UpdateColumn("total_sold", gorm.Expr("total_sold + ?", count)).Error } func (s *JSONStore) IncrementView(id, fingerprint string) (models.Product, bool, error) { s.mu.Lock() defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { - return models.Product{}, false, err - } - now := time.Now() s.cleanupRecentViews(now) key := buildViewKey(id, fingerprint) if lastViewedAt, ok := s.recentViews[key]; ok && now.Sub(lastViewedAt) < viewCooldown { - for _, item := range items { - if item.ID == id { - return item, false, nil - } + var row database.ProductRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Product{}, false, fmt.Errorf("product not found") } + return rowToModel(row, nil), false, nil + } + + if err := s.db.Model(&database.ProductRow{}).Where("id = ?", id). + UpdateColumn("view_count", gorm.Expr("view_count + 1")).Error; err != nil { + return models.Product{}, false, err + } + s.recentViews[key] = now + + var row database.ProductRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { return models.Product{}, false, fmt.Errorf("product not found") } - - for i, item := range items { - if item.ID == id { - item.ViewCount++ - item.UpdatedAt = now - items[i] = item - s.recentViews[key] = now - if err := s.writeAll(items); err != nil { - return models.Product{}, false, err - } - return item, true, nil - } - } - - return models.Product{}, false, fmt.Errorf("product not found") + return rowToModel(row, nil), true, nil } func (s *JSONStore) Delete(id string) error { - s.mu.Lock() - defer s.mu.Unlock() - items, err := s.readAll() - if err != nil { + if err := s.db.Where("product_id = ?", id).Delete(&database.ProductCodeRow{}).Error; err != nil { return err } - filtered := make([]models.Product, 0, len(items)) - for _, item := range items { - if item.ID != id { - filtered = append(filtered, item) - } - } - if err := s.writeAll(filtered); err != nil { - return err - } - return nil -} - -func (s *JSONStore) readAll() ([]models.Product, error) { - bytes, err := os.ReadFile(s.path) - if err != nil { - return nil, fmt.Errorf("read products: %w", err) - } - var items []models.Product - if err := json.Unmarshal(bytes, &items); err != nil { - return nil, fmt.Errorf("parse products: %w", err) - } - for i, item := range items { - items[i] = normalizeProduct(item) - } - return items, nil -} - -func (s *JSONStore) writeAll(items []models.Product) error { - for i, item := range items { - items[i] = normalizeProduct(item) - } - bytes, err := json.MarshalIndent(items, "", " ") - if err != nil { - return fmt.Errorf("encode products: %w", err) - } - if err := os.WriteFile(s.path, bytes, 0o644); err != nil { - return fmt.Errorf("write products: %w", err) - } - return nil + return s.db.Delete(&database.ProductRow{}, "id = ?", id).Error } +// normalizeProduct cleans up product fields (same logic as before, no file I/O). func normalizeProduct(item models.Product) models.Product { item.CoverURL = strings.TrimSpace(item.CoverURL) if item.CoverURL == "" { @@ -276,6 +272,9 @@ func normalizeProduct(item models.Product) models.Product { item.VerificationURL = strings.TrimSpace(item.VerificationURL) item.Codes = sanitizeCodes(item.Codes) item.Quantity = len(item.Codes) + if item.DeliveryMode == "" { + item.DeliveryMode = "auto" + } return item } @@ -284,10 +283,7 @@ func sanitizeCodes(codes []string) []string { seen := map[string]bool{} for _, code := range codes { trimmed := strings.TrimSpace(code) - if trimmed == "" { - continue - } - if seen[trimmed] { + if trimmed == "" || seen[trimmed] { continue } seen[trimmed] = true diff --git a/mengyastore-backend/internal/storage/orderstore.go b/mengyastore-backend/internal/storage/orderstore.go index 2df24ec..ea43ee1 100644 --- a/mengyastore-backend/internal/storage/orderstore.go +++ b/mengyastore-backend/internal/storage/orderstore.go @@ -1,140 +1,139 @@ package storage import ( - "encoding/json" "fmt" - "os" - "path/filepath" - "sync" - "time" "github.com/google/uuid" + "gorm.io/gorm" + "mengyastore-backend/internal/database" "mengyastore-backend/internal/models" ) type OrderStore struct { - path string - mu sync.Mutex + db *gorm.DB } -func NewOrderStore(path string) (*OrderStore, error) { - if err := ensureOrdersFile(path); err != nil { - return nil, err - } - return &OrderStore{path: path}, nil +func NewOrderStore(db *gorm.DB) (*OrderStore, error) { + return &OrderStore{db: db}, nil } -func (s *OrderStore) Count() (int, error) { - s.mu.Lock() - defer s.mu.Unlock() - - items, err := s.readAll() - if err != nil { - return 0, err +func orderRowToModel(row database.OrderRow) models.Order { + return models.Order{ + ID: row.ID, + ProductID: row.ProductID, + ProductName: row.ProductName, + UserAccount: row.UserAccount, + UserName: row.UserName, + Quantity: row.Quantity, + DeliveredCodes: row.DeliveredCodes, + Status: row.Status, + DeliveryMode: row.DeliveryMode, + Note: row.Note, + ContactPhone: row.ContactPhone, + ContactEmail: row.ContactEmail, + NotifyEmail: row.NotifyEmail, + CreatedAt: row.CreatedAt, } - return len(items), nil -} - -func (s *OrderStore) ListByAccount(account string) ([]models.Order, error) { - s.mu.Lock() - defer s.mu.Unlock() - - items, err := s.readAll() - if err != nil { - return nil, err - } - matched := make([]models.Order, 0) - for i := len(items) - 1; i >= 0; i-- { - if items[i].UserAccount == account { - matched = append(matched, items[i]) - } - } - return matched, nil -} - -func (s *OrderStore) Confirm(id string) (models.Order, error) { - s.mu.Lock() - defer s.mu.Unlock() - - items, err := s.readAll() - if err != nil { - return models.Order{}, err - } - for i, item := range items { - if item.ID == id { - if item.Status == "completed" { - return item, nil - } - items[i].Status = "completed" - if err := s.writeAll(items); err != nil { - return models.Order{}, err - } - return items[i], nil - } - } - return models.Order{}, fmt.Errorf("order not found") } func (s *OrderStore) Create(order models.Order) (models.Order, error) { - s.mu.Lock() - defer s.mu.Unlock() - - items, err := s.readAll() - if err != nil { - return models.Order{}, err - } - - order.ID = uuid.NewString() - order.CreatedAt = time.Now() - items = append(items, order) - if err := s.writeAll(items); err != nil { + if order.ID == "" { + order.ID = uuid.NewString() + } + if len(order.DeliveredCodes) == 0 { + order.DeliveredCodes = []string{} + } + row := database.OrderRow{ + ID: order.ID, + ProductID: order.ProductID, + ProductName: order.ProductName, + UserAccount: order.UserAccount, + UserName: order.UserName, + Quantity: order.Quantity, + DeliveredCodes: database.StringSlice(order.DeliveredCodes), + Status: order.Status, + DeliveryMode: order.DeliveryMode, + Note: order.Note, + ContactPhone: order.ContactPhone, + ContactEmail: order.ContactEmail, + NotifyEmail: order.NotifyEmail, + } + if err := s.db.Create(&row).Error; err != nil { return models.Order{}, err } + order.CreatedAt = row.CreatedAt return order, nil } -func (s *OrderStore) readAll() ([]models.Order, error) { - bytes, err := os.ReadFile(s.path) - if err != nil { - return nil, fmt.Errorf("read orders: %w", err) +func (s *OrderStore) GetByID(id string) (models.Order, error) { + var row database.OrderRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Order{}, fmt.Errorf("order not found") } - var items []models.Order - if err := json.Unmarshal(bytes, &items); err != nil { - return nil, fmt.Errorf("parse orders: %w", err) - } - return items, nil + return orderRowToModel(row), nil } -func (s *OrderStore) writeAll(items []models.Order) error { - bytes, err := json.MarshalIndent(items, "", " ") - if err != nil { - return fmt.Errorf("encode orders: %w", err) +func (s *OrderStore) Confirm(id string) (models.Order, error) { + var row database.OrderRow + if err := s.db.First(&row, "id = ?", id).Error; err != nil { + return models.Order{}, fmt.Errorf("order not found") } - if err := os.WriteFile(s.path, bytes, 0o644); err != nil { - return fmt.Errorf("write orders: %w", err) + if err := s.db.Model(&row).Update("status", "completed").Error; err != nil { + return models.Order{}, err } - return nil + row.Status = "completed" + return orderRowToModel(row), nil } -func ensureOrdersFile(path string) error { - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("mkdir data dir: %w", err) +func (s *OrderStore) ListByAccount(account string) ([]models.Order, error) { + var rows []database.OrderRow + if err := s.db.Where("user_account = ?", account).Order("created_at DESC").Find(&rows).Error; err != nil { + return nil, err } - if _, err := os.Stat(path); err == nil { - return nil - } else if !os.IsNotExist(err) { - return fmt.Errorf("stat data file: %w", err) + orders := make([]models.Order, len(rows)) + for i, r := range rows { + orders[i] = orderRowToModel(r) } - - initial := []models.Order{} - bytes, err := json.MarshalIndent(initial, "", " ") - if err != nil { - return fmt.Errorf("init json: %w", err) - } - if err := os.WriteFile(path, bytes, 0o644); err != nil { - return fmt.Errorf("write init json: %w", err) - } - return nil + return orders, nil +} + +func (s *OrderStore) ListAll() ([]models.Order, error) { + var rows []database.OrderRow + if err := s.db.Order("created_at DESC").Find(&rows).Error; err != nil { + return nil, err + } + orders := make([]models.Order, len(rows)) + for i, r := range rows { + orders[i] = orderRowToModel(r) + } + return orders, nil +} + +func (s *OrderStore) CountPurchasedByAccount(account, productID string) (int, error) { + var total int64 + err := s.db.Model(&database.OrderRow{}). + Where("user_account = ? AND product_id = ? AND status = ?", account, productID, "completed"). + Select("COALESCE(SUM(quantity), 0)").Scan(&total).Error + return int(total), err +} + +// Count returns the total number of orders. +func (s *OrderStore) Count() (int, error) { + var count int64 + if err := s.db.Model(&database.OrderRow{}).Count(&count).Error; err != nil { + return 0, err + } + return int(count), nil +} + +// Delete removes a single order by ID. +func (s *OrderStore) Delete(id string) error { + return s.db.Delete(&database.OrderRow{}, "id = ?", id).Error +} + +// UpdateCodes replaces the delivered codes for an order (used by auto-delivery to set codes after extracting). +func (s *OrderStore) UpdateCodes(id string, codes []string) error { + return s.db.Model(&database.OrderRow{}).Where("id = ?", id). + Update("delivered_codes", database.StringSlice(codes)).Error } diff --git a/mengyastore-backend/internal/storage/sitestore.go b/mengyastore-backend/internal/storage/sitestore.go index 984a1e5..5c711da 100644 --- a/mengyastore-backend/internal/storage/sitestore.go +++ b/mengyastore-backend/internal/storage/sitestore.go @@ -1,128 +1,135 @@ package storage import ( - "crypto/sha256" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sync" - "time" + "strconv" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + + "mengyastore-backend/internal/database" ) -const visitCooldown = 6 * time.Hour - -type siteData struct { - TotalVisits int `json:"totalVisits"` -} - type SiteStore struct { - path string - mu sync.Mutex - recentVisits map[string]time.Time + db *gorm.DB } -func NewSiteStore(path string) (*SiteStore, error) { - if err := ensureSiteFile(path); err != nil { - return nil, err - } - return &SiteStore{ - path: path, - recentVisits: make(map[string]time.Time), - }, nil +func NewSiteStore(db *gorm.DB) (*SiteStore, error) { + return &SiteStore{db: db}, nil } -func (s *SiteStore) RecordVisit(fingerprint string) (int, bool, error) { - s.mu.Lock() - defer s.mu.Unlock() - - now := time.Now() - s.cleanupRecentVisits(now) - - key := buildSiteVisitKey(fingerprint) - if last, ok := s.recentVisits[key]; ok && now.Sub(last) < visitCooldown { - data, err := s.read() - if err != nil { - return 0, false, err - } - return data.TotalVisits, false, nil +func (s *SiteStore) get(key string) (string, error) { + var row database.SiteSettingRow + if err := s.db.First(&row, "key = ?", key).Error; err != nil { + return "", nil // key not found → return zero value } + return row.Value, nil +} - data, err := s.read() - if err != nil { - return 0, false, err - } - data.TotalVisits++ - s.recentVisits[key] = now - if err := s.write(data); err != nil { - return 0, false, err - } - return data.TotalVisits, true, nil +func (s *SiteStore) set(key, value string) error { + return s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "key"}}, + DoUpdates: clause.AssignmentColumns([]string{"value"}), + }).Create(&database.SiteSettingRow{Key: key, Value: value}).Error } func (s *SiteStore) GetTotalVisits() (int, error) { - s.mu.Lock() - defer s.mu.Unlock() - data, err := s.read() + v, err := s.get("totalVisits") + if err != nil || v == "" { + return 0, err + } + n, _ := strconv.Atoi(v) + return n, nil +} + +func (s *SiteStore) IncrementVisits() (int, error) { + current, err := s.GetTotalVisits() if err != nil { return 0, err } - return data.TotalVisits, nil + current++ + if err := s.set("totalVisits", strconv.Itoa(current)); err != nil { + return 0, err + } + return current, nil } -func (s *SiteStore) read() (siteData, error) { - bytes, err := os.ReadFile(s.path) +func (s *SiteStore) GetMaintenance() (enabled bool, reason string, err error) { + v, err := s.get("maintenance") if err != nil { - return siteData{}, fmt.Errorf("read site data: %w", err) + return false, "", err } - var data siteData - if err := json.Unmarshal(bytes, &data); err != nil { - return siteData{}, fmt.Errorf("parse site data: %w", err) - } - return data, nil + enabled = v == "true" + reason, err = s.get("maintenanceReason") + return enabled, reason, err } -func (s *SiteStore) write(data siteData) error { - bytes, err := json.MarshalIndent(data, "", " ") - if err != nil { - return fmt.Errorf("encode site data: %w", err) +func (s *SiteStore) SetMaintenance(enabled bool, reason string) error { + v := "false" + if enabled { + v = "true" } - if err := os.WriteFile(s.path, bytes, 0o644); err != nil { - return fmt.Errorf("write site data: %w", err) + if err := s.set("maintenance", v); err != nil { + return err } - return nil + return s.set("maintenanceReason", reason) } -func (s *SiteStore) cleanupRecentVisits(now time.Time) { - for key, last := range s.recentVisits { - if now.Sub(last) >= visitCooldown { - delete(s.recentVisits, key) +// RecordVisit increments the visit counter. Returns (totalVisits, counted, error). +// For simplicity, every call increments (fingerprint dedup is handled in-memory by the handler layer). +func (s *SiteStore) RecordVisit(_ string) (int, bool, error) { + total, err := s.IncrementVisits() + return total, true, err +} + +// SMTPConfig holds the mail sender configuration stored in the DB. +type SMTPConfig struct { + Email string `json:"email"` + Password string `json:"password"` + FromName string `json:"fromName"` + Host string `json:"host"` + Port string `json:"port"` +} + +// IsConfiguredEmail returns true if the SMTP config is ready to send mail. +func (c SMTPConfig) IsConfiguredEmail() bool { + return c.Email != "" && c.Password != "" && c.Host != "" +} + +func (s *SiteStore) GetSMTPConfig() (SMTPConfig, error) { + cfg := SMTPConfig{ + Host: "smtp.qq.com", + Port: "465", + } + if v, _ := s.get("smtpEmail"); v != "" { + cfg.Email = v + } + if v, _ := s.get("smtpPassword"); v != "" { + cfg.Password = v + } + if v, _ := s.get("smtpFromName"); v != "" { + cfg.FromName = v + } + if v, _ := s.get("smtpHost"); v != "" { + cfg.Host = v + } + if v, _ := s.get("smtpPort"); v != "" { + cfg.Port = v + } + return cfg, nil +} + +func (s *SiteStore) SetSMTPConfig(cfg SMTPConfig) error { + pairs := [][2]string{ + {"smtpEmail", cfg.Email}, + {"smtpPassword", cfg.Password}, + {"smtpFromName", cfg.FromName}, + {"smtpHost", cfg.Host}, + {"smtpPort", cfg.Port}, + } + for _, p := range pairs { + if err := s.set(p[0], p[1]); err != nil { + return err } } -} - -func buildSiteVisitKey(fingerprint string) string { - sum := sha256.Sum256([]byte("site|" + fingerprint)) - return fmt.Sprintf("%x", sum) -} - -func ensureSiteFile(path string) error { - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("mkdir data dir: %w", err) - } - if _, err := os.Stat(path); err == nil { - return nil - } else if !os.IsNotExist(err) { - return fmt.Errorf("stat site file: %w", err) - } - initial := siteData{TotalVisits: 0} - bytes, err := json.MarshalIndent(initial, "", " ") - if err != nil { - return fmt.Errorf("init site json: %w", err) - } - if err := os.WriteFile(path, bytes, 0o644); err != nil { - return fmt.Errorf("write site json: %w", err) - } return nil } diff --git a/mengyastore-backend/internal/storage/wishliststore.go b/mengyastore-backend/internal/storage/wishliststore.go new file mode 100644 index 0000000..96a1a72 --- /dev/null +++ b/mengyastore-backend/internal/storage/wishliststore.go @@ -0,0 +1,38 @@ +package storage + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" + + "mengyastore-backend/internal/database" +) + +type WishlistStore struct { + db *gorm.DB +} + +func NewWishlistStore(db *gorm.DB) (*WishlistStore, error) { + return &WishlistStore{db: db}, nil +} + +func (s *WishlistStore) Get(accountID string) ([]string, error) { + var rows []database.WishlistRow + if err := s.db.Where("account_id = ?", accountID).Find(&rows).Error; err != nil { + return nil, err + } + ids := make([]string, len(rows)) + for i, r := range rows { + ids[i] = r.ProductID + } + return ids, nil +} + +func (s *WishlistStore) Add(accountID, productID string) error { + return s.db.Clauses(clause.OnConflict{DoNothing: true}). + Create(&database.WishlistRow{AccountID: accountID, ProductID: productID}).Error +} + +func (s *WishlistStore) Remove(accountID, productID string) error { + return s.db.Where("account_id = ? AND product_id = ?", accountID, productID). + Delete(&database.WishlistRow{}).Error +} diff --git a/mengyastore-backend/main.go b/mengyastore-backend/main.go index 1f519b8..c1eac64 100644 --- a/mengyastore-backend/main.go +++ b/mengyastore-backend/main.go @@ -10,6 +10,7 @@ import ( "mengyastore-backend/internal/auth" "mengyastore-backend/internal/config" + "mengyastore-backend/internal/database" "mengyastore-backend/internal/handlers" "mengyastore-backend/internal/storage" ) @@ -20,18 +21,32 @@ func main() { log.Fatalf("load config failed: %v", err) } - store, err := storage.NewJSONStore("data/json/products.json") + // Initialise database + db, err := database.Open(cfg.DatabaseDSN) + if err != nil { + log.Fatalf("init database failed: %v", err) + } + + store, err := storage.NewJSONStore(db) if err != nil { log.Fatalf("init store failed: %v", err) } - orderStore, err := storage.NewOrderStore("data/json/orders.json") + orderStore, err := storage.NewOrderStore(db) if err != nil { log.Fatalf("init order store failed: %v", err) } - siteStore, err := storage.NewSiteStore("data/json/site.json") + siteStore, err := storage.NewSiteStore(db) if err != nil { log.Fatalf("init site store failed: %v", err) } + wishlistStore, err := storage.NewWishlistStore(db) + if err != nil { + log.Fatalf("init wishlist store failed: %v", err) + } + chatStore, err := storage.NewChatStore(db) + if err != nil { + log.Fatalf("init chat store failed: %v", err) + } r := gin.Default() r.Use(cors.New(cors.Config{ @@ -50,15 +65,18 @@ func main() { authClient := auth.NewSproutGateClient(cfg.AuthAPIURL) publicHandler := handlers.NewPublicHandler(store) - adminHandler := handlers.NewAdminHandler(store, cfg) - orderHandler := handlers.NewOrderHandler(store, orderStore, authClient) + adminHandler := handlers.NewAdminHandler(store, cfg, siteStore, orderStore, chatStore) + orderHandler := handlers.NewOrderHandler(store, orderStore, siteStore, authClient) statsHandler := handlers.NewStatsHandler(orderStore, siteStore) + wishlistHandler := handlers.NewWishlistHandler(wishlistStore, authClient) + chatHandler := handlers.NewChatHandler(chatStore, authClient) r.GET("/api/products", publicHandler.ListProducts) r.POST("/api/checkout", orderHandler.CreateOrder) r.POST("/api/products/:id/view", publicHandler.RecordProductView) r.GET("/api/stats", statsHandler.GetStats) r.POST("/api/site/visit", statsHandler.RecordVisit) + r.GET("/api/site/maintenance", statsHandler.GetMaintenance) r.GET("/api/orders", orderHandler.ListMyOrders) r.POST("/api/orders/:id/confirm", orderHandler.ConfirmOrder) @@ -68,6 +86,25 @@ func main() { r.PUT("/api/admin/products/:id", adminHandler.UpdateProduct) r.PATCH("/api/admin/products/:id/status", adminHandler.ToggleProduct) r.DELETE("/api/admin/products/:id", adminHandler.DeleteProduct) + r.POST("/api/admin/site/maintenance", adminHandler.SetMaintenance) + r.GET("/api/admin/site/smtp", adminHandler.GetSMTPConfig) + r.POST("/api/admin/site/smtp", adminHandler.SetSMTPConfig) + r.GET("/api/admin/orders", adminHandler.ListAllOrders) + r.DELETE("/api/admin/orders/:id", adminHandler.DeleteOrder) + + r.GET("/api/wishlist", wishlistHandler.GetWishlist) + r.POST("/api/wishlist", wishlistHandler.AddToWishlist) + r.DELETE("/api/wishlist/:id", wishlistHandler.RemoveFromWishlist) + + // Chat routes (user) + r.GET("/api/chat/messages", chatHandler.GetMyMessages) + r.POST("/api/chat/messages", chatHandler.SendMyMessage) + + // Chat routes (admin) + r.GET("/api/admin/chat", adminHandler.GetAllConversations) + r.GET("/api/admin/chat/:account", adminHandler.GetConversation) + r.POST("/api/admin/chat/:account", adminHandler.AdminReply) + r.DELETE("/api/admin/chat/:account", adminHandler.ClearConversation) log.Println("萌芽小店后端启动于 http://localhost:8080") if err := r.Run(":8080"); err != nil { diff --git a/mengyastore-backend/mengyastore-backend.exe b/mengyastore-backend/mengyastore-backend.exe deleted file mode 100644 index 0457afa..0000000 Binary files a/mengyastore-backend/mengyastore-backend.exe and /dev/null differ diff --git a/mengyastore-backend/mengyastore-backend.exe~ b/mengyastore-backend/mengyastore-backend.exe~ deleted file mode 100644 index 63d66f4..0000000 Binary files a/mengyastore-backend/mengyastore-backend.exe~ and /dev/null differ diff --git a/mengyastore-frontend/README.md b/mengyastore-frontend/README.md new file mode 100644 index 0000000..4f173c3 --- /dev/null +++ b/mengyastore-frontend/README.md @@ -0,0 +1,167 @@ +# 萌芽小店 · 前端 + +基于 **Vue 3 + Vite** 构建的数字商品销售平台前端,采用组件化模块架构。 + +## 技术依赖 + +| 包 | 版本 | 用途 | +|----|------|------| +| vue | ^3.x | 核心框架 | +| vite | ^5.x | 构建工具 | +| vue-router | ^4.x | 路由管理 | +| pinia | ^2.x | 状态管理 | +| axios | ^1.6 | HTTP 请求 | +| markdown-it | ^14 | Markdown 渲染 | + +## 目录结构 + +``` +src/ +├── assets/ +│ └── styles.css # 全局 CSS 变量与公共样式 +├── router/ +│ └── index.js # 路由配置(含维护模式守卫) +├── modules/ +│ ├── shared/ +│ │ ├── api.js # 所有 API 请求封装 +│ │ ├── auth.js # 认证状态(Pinia store) +│ │ └── useWishlist.js # 收藏夹 Composable +│ ├── store/ +│ │ ├── StorePage.vue # 商品列表页 +│ │ ├── ProductDetail.vue # 商品详情页 +│ │ ├── CheckoutPage.vue # 结算页 +│ │ └── components/ +│ │ └── ProductCard.vue # 商品卡片组件 +│ ├── admin/ +│ │ ├── AdminPage.vue # 管理后台(编排层) +│ │ └── components/ +│ │ ├── AdminTokenRow.vue # 令牌输入 +│ │ ├── AdminMaintenanceRow.vue # 维护模式开关 +│ │ ├── AdminProductTable.vue # 商品列表表格 +│ │ ├── AdminProductModal.vue # 商品编辑弹窗 +│ │ ├── AdminOrderTable.vue # 订单记录表格 +│ │ └── AdminChatPanel.vue # 用户聊天管理 +│ ├── user/ +│ │ └── MyOrdersPage.vue # 我的订单 +│ ├── wishlist/ +│ │ └── WishlistPage.vue # 收藏夹页面 +│ ├── maintenance/ +│ │ └── MaintenancePage.vue # 站点维护页面 +│ └── chat/ +│ └── ChatWidget.vue # 用户悬浮聊天窗口 +└── App.vue # 根组件(全局布局 + 导航) +``` + +## 路由列表 + +| 路径 | 组件 | 说明 | +|------|------|------| +| `/` | `StorePage` | 商品列表 | +| `/product/:id` | `ProductDetail` | 商品详情 | +| `/product/:id/checkout` | `CheckoutPage` | 结算页 | +| `/my/orders` | `MyOrdersPage` | 我的订单(需登录)| +| `/wishlist` | `WishlistPage` | 收藏夹(需登录)| +| `/admin` | `AdminPage` | 管理后台(需令牌)| +| `/maintenance` | `MaintenancePage` | 维护中页面 | + +### 路由守卫 + +- **维护模式**:`beforeEach` 检查 `GET /api/site/maintenance`,若站点维护中则重定向到 `/maintenance`(管理员访问 `/admin` 时豁免) +- 豁免路径:`/maintenance`、`/wishlist`、`/admin` + +## 认证流程 + +使用 SproutGate OAuth 服务: + +1. 未登录用户点击「萌芽账号登录」,跳转到 `https://auth.shumengya.top/login?callback=<当前页>` +2. 登录成功后回调携带 token,存入 localStorage +3. `authState`(reactive 对象)全局共享 `token`、`account`、`username`、`avatarUrl` +4. 所有需要认证的 API 请求在 `Authorization: Bearer ` 头部携带 token + +## 主要功能模块 + +### 商品列表(StorePage) + +- 视图模式(`viewMode`):全部 / 免费 / 新上架 / 最多购买 / 最多浏览 / 价格最高 / 价格最低 +- 搜索:实时过滤商品名称 +- 分页:桌面 20 条/页(4×5),手机 10 条/页(5×2) +- 响应式布局:`window.innerWidth <= 900` 切换为双列 + +### 结算页(CheckoutPage) + +- 展示商品信息与价格 +- 数量输入,最大值受 `product.maxPerAccount` 限制 +- 若商品开启 `showNote`:展示备注文本框 +- 若商品开启 `showContact`:展示手机号和邮箱输入框 +- 手动发货商品:展示蓝色提示条,确认后显示"等待发货中" +- 自动发货商品:确认后展示卡密内容 + +### 收藏夹(useWishlist Composable) + +```js +import { wishlistIds, wishlistCount, inWishlist, loadWishlist, toggleWishlist } from './useWishlist' +``` + +- 数据存储在后端,通过 API 同步 +- `App.vue` 在登录状态变化时自动调用 `loadWishlist()` +- 收藏状态通过 `wishlistSet`(computed Set)提供 O(1) 查找 + +### 客服聊天(ChatWidget) + +- 仅已登录用户显示(右下角悬浮按钮) +- 每 5 秒轮询新消息 +- 客户端和服务端均限制每秒最多发送 1 条消息 + +## 开发启动 + +```bash +npm install +npm run dev # 开发服务器 :5173 +npm run build # 生产构建 → dist/ +npm run preview # 预览构建结果 +``` + +### 环境变量 + +在项目根目录创建 `.env.local`: + +```env +VITE_API_BASE_URL=http://localhost:8080 +``` + +生产部署时设置实际 API 地址。 + +## 全局样式 + +`src/assets/styles.css` 定义了全局 CSS 变量: + +```css +:root { + --accent: #91a8d0; /* 主题色 */ + --accent-2: #b8c8e4; /* 渐变色 */ + --text: #2c2c3a; /* 文字色 */ + --muted: #8e8e9e; /* 次要文字 */ + --line: rgba(0,0,0,0.08); /* 边框色 */ + --radius: 8px; /* 圆角 */ +} +``` + +字体使用楷体(KaiTi / STKaiti),fallback 到系统衬线字体。 + +## 构建与部署 + +```bash +npm run build +``` + +产物在 `dist/` 目录,为静态文件,部署到 Nginx / CDN 时需配置: + +```nginx +location / { + try_files $uri $uri/ /index.html; # SPA 路由支持 +} + +location /api/ { + proxy_pass http://localhost:8080; # 反向代理到后端 +} +``` diff --git a/mengyastore-frontend/index.html b/mengyastore-frontend/index.html index 8c1f5dc..7364f63 100644 --- a/mengyastore-frontend/index.html +++ b/mengyastore-frontend/index.html @@ -2,10 +2,24 @@ - + 萌芽小店 - - + + + + + + + + + + + + + + + +
diff --git a/mengyastore-frontend/package-lock.json b/mengyastore-frontend/package-lock.json index 2d27d5b..de962d6 100644 --- a/mengyastore-frontend/package-lock.json +++ b/mengyastore-frontend/package-lock.json @@ -15,8 +15,357 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@vite-pwa/assets-generator": "^1.0.2", "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.2.7" + "vite": "^5.2.7", + "vite-plugin-pwa": "^1.2.0" + } + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { @@ -37,6 +386,45 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", @@ -52,6 +440,1162 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", @@ -65,6 +1609,24 @@ "node": ">=6.9.0" } }, + "node_modules/@canvas/image-data": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.1.0.tgz", + "integrity": "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -456,12 +2018,576 @@ "node": ">=12" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@quansync/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -812,6 +2938,29 @@ "win32" ] }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -819,6 +2968,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vite-pwa/assets-generator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz", + "integrity": "sha512-MCbrb508JZHqe7bUibmZj/lyojdhLRnfkmyXnkrCM2zVrjTgL89U8UEfInpKTvPeTnxsw2hmyZxnhsdNR6yhwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "colorette": "^2.0.20", + "consola": "^3.4.2", + "sharp": "^0.33.5", + "sharp-ico": "^0.1.5", + "unconfig": "^7.3.1" + }, + "bin": { + "pwa-assets-generator": "bin/pwa-assets-generator.mjs" + }, + "engines": { + "node": ">=16.14.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -951,18 +3138,130 @@ "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", "license": "MIT" }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", @@ -974,6 +3273,164 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -987,6 +3444,96 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -999,12 +3546,239 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-bmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/decode-bmp/-/decode-bmp-0.2.1.tgz", + "integrity": "sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@canvas/image-data": "^1.0.0", + "to-data-view": "^1.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/decode-ico": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/decode-ico/-/decode-ico-0.4.1.tgz", + "integrity": "sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@canvas/image-data": "^1.0.0", + "decode-bmp": "^0.2.0", + "to-data-view": "^1.1.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1014,6 +3788,16 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1028,6 +3812,29 @@ "node": ">= 0.4" } }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1040,6 +3847,75 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1085,6 +3961,24 @@ "node": ">= 0.4" } }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1124,12 +4018,121 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1150,6 +4153,39 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -1166,6 +4202,22 @@ "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1190,6 +4242,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1214,6 +4317,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -1227,6 +4337,66 @@ "node": ">= 0.4" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1239,6 +4409,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1278,6 +4497,579 @@ "node": ">= 0.4" } }, + "node_modules/ico-endec": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ico-endec/-/ico-endec-0.1.6.tgz", + "integrity": "sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==", + "dev": true, + "license": "MPL-2.0" + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1287,6 +5079,37 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1349,6 +5172,39 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1367,12 +5223,145 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pinia": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", @@ -1395,6 +5384,16 @@ } } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -1423,12 +5422,35 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -1438,6 +5460,166 @@ "node": ">=6" } }, + "node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -1483,6 +5665,352 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/sharp-ico": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/sharp-ico/-/sharp-ico-0.1.5.tgz", + "integrity": "sha512-a3jODQl82NPp1d5OYb0wY+oFaPk7AvyxipIowCHk7pBsZCWgbe0yAkU2OOXdoH0ENyANhyOQbs9xkAiRHcF02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "decode-ico": "*", + "ico-endec": "*", + "sharp": "*" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/smob": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", + "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1492,12 +6020,520 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-data-view": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz", + "integrity": "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unconfig": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.5.0.tgz", + "integrity": "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "defu": "^6.1.4", + "jiti": "^2.6.1", + "quansync": "^1.0.0", + "unconfig-core": "7.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -1558,6 +6594,37 @@ } } }, + "node_modules/vite-plugin-pwa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz", + "integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.10", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^1.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, "node_modules/vue": { "version": "3.5.30", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz", @@ -1619,6 +6686,466 @@ "peerDependencies": { "vue": "^3.5.0" } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-build": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.79.2", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/workbox-build/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" } } } diff --git a/mengyastore-frontend/package.json b/mengyastore-frontend/package.json index 7da74d4..ba7a21b 100644 --- a/mengyastore-frontend/package.json +++ b/mengyastore-frontend/package.json @@ -16,7 +16,9 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@vite-pwa/assets-generator": "^1.0.2", "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.2.7" + "vite": "^5.2.7", + "vite-plugin-pwa": "^1.2.0" } } diff --git a/mengyastore-frontend/public/apple-touch-icon-180x180.png b/mengyastore-frontend/public/apple-touch-icon-180x180.png new file mode 100644 index 0000000..1f28965 Binary files /dev/null and b/mengyastore-frontend/public/apple-touch-icon-180x180.png differ diff --git a/mengyastore-frontend/public/favicon.ico b/mengyastore-frontend/public/favicon.ico new file mode 100644 index 0000000..01530cd Binary files /dev/null and b/mengyastore-frontend/public/favicon.ico differ diff --git a/mengyastore-frontend/public/icon.svg b/mengyastore-frontend/public/icon.svg new file mode 100644 index 0000000..8f59aff --- /dev/null +++ b/mengyastore-frontend/public/icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/mengyastore-frontend/public/maskable-icon-512x512.png b/mengyastore-frontend/public/maskable-icon-512x512.png new file mode 100644 index 0000000..9760b03 Binary files /dev/null and b/mengyastore-frontend/public/maskable-icon-512x512.png differ diff --git a/mengyastore-frontend/public/pwa-192x192.png b/mengyastore-frontend/public/pwa-192x192.png new file mode 100644 index 0000000..0b537a2 Binary files /dev/null and b/mengyastore-frontend/public/pwa-192x192.png differ diff --git a/mengyastore-frontend/public/pwa-512x512.png b/mengyastore-frontend/public/pwa-512x512.png new file mode 100644 index 0000000..86d068f Binary files /dev/null and b/mengyastore-frontend/public/pwa-512x512.png differ diff --git a/mengyastore-frontend/public/pwa-64x64.png b/mengyastore-frontend/public/pwa-64x64.png new file mode 100644 index 0000000..4dc5566 Binary files /dev/null and b/mengyastore-frontend/public/pwa-64x64.png differ diff --git a/mengyastore-frontend/src/App.vue b/mengyastore-frontend/src/App.vue index 074066e..e814f7b 100644 --- a/mengyastore-frontend/src/App.vue +++ b/mengyastore-frontend/src/App.vue @@ -1,5 +1,8 @@ @@ -108,8 +153,12 @@ const loggedIn = computed(() => isLoggedIn()) const loginUrl = computed(() => getLoginUrl()) const form = reactive({ - quantity: 1 + quantity: 1, + note: '', + contactPhone: '', + contactEmail: '' }) +const isManualDelivery = ref(false) const renderMarkdown = (content) => md.render(content || '') @@ -145,7 +194,11 @@ const submitOrder = async () => { try { const payload = { productId: product.value.id, - quantity: form.quantity + quantity: form.quantity, + note: form.note || '', + contactPhone: form.contactPhone || '', + contactEmail: form.contactEmail || '', + notifyEmail: authState.email || '' } const token = isLoggedIn() ? authState.token : null const response = await createOrder(payload, token) @@ -166,6 +219,7 @@ const doConfirm = async () => { try { const result = await confirmOrder(orderResult.value.orderId) deliveredCodes.value = result.deliveredCodes || [] + isManualDelivery.value = result.isManual || result.deliveryMode === 'manual' confirmed.value = true } catch (err) { confirmError.value = err?.response?.data?.error || '确认失败,请重试' @@ -213,7 +267,7 @@ onMounted(async () => { width: 140px; height: 140px; object-fit: cover; - border-radius: 12px; + border-radius: 8px; } .summary-content { @@ -248,7 +302,7 @@ onMounted(async () => { max-width: 320px; margin: 12px auto; width: 100%; - border-radius: 14px; + border-radius: 8px; border: 1px solid var(--line); } @@ -266,7 +320,7 @@ onMounted(async () => { border-radius: 999px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: white; - font-size: 13px; + font-size: 15px; font-weight: 600; margin-bottom: 12px; } @@ -283,18 +337,18 @@ onMounted(async () => { width: min(520px, 100%); min-height: 120px; padding: 14px 16px; - border-radius: 14px; + border-radius: 8px; border: 1px solid var(--line); background: rgba(255, 255, 255, 0.9); color: var(--text); - font-size: 14px; + font-size: 16px; line-height: 1.7; resize: none; } .error { color: #d64848; - font-size: 13px; + font-size: 15px; } .status { @@ -307,6 +361,87 @@ onMounted(async () => { font-weight: 900; } +.require-login-block { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 32px 24px; + text-align: center; + border: 1px solid var(--line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.6); +} + +.require-login-title { + font-size: 18px; + font-weight: 600; + color: var(--text); +} + +.btn-inline { + display: inline-block; + text-decoration: none; + padding: 10px 28px; + border-radius: 8px; + font-size: 16px; + font-weight: 700; + cursor: pointer; +} + +.limit-hint { + color: var(--accent); +} + +textarea { + width: 100%; + box-sizing: border-box; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid var(--line); + font-size: 15px; + font-family: inherit; + background: rgba(255, 255, 255, 0.9); + color: var(--text); + resize: vertical; +} + +.contact-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.delivery-tip { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 14px; + background: rgba(90, 120, 200, 0.08); + border: 1px solid rgba(90, 120, 200, 0.2); + border-radius: 8px; + color: #5a78c8; + font-size: 14px; +} + +.manual-delivery-notice { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + background: rgba(90, 180, 120, 0.08); + border: 1px solid rgba(90, 180, 120, 0.25); + border-radius: 8px; + color: var(--text); +} + +.manual-delivery-title { + font-size: 16px; + font-weight: 600; + color: #3a9a68; + margin: 0 0 4px 0; +} + @media (max-width: 900px) { .checkout-grid { grid-template-columns: 1fr; @@ -321,5 +456,9 @@ onMounted(async () => { width: 100%; height: 200px; } + + .contact-row { + grid-template-columns: 1fr; + } } diff --git a/mengyastore-frontend/src/modules/store/ProductDetail.vue b/mengyastore-frontend/src/modules/store/ProductDetail.vue index 636eac1..ab27d30 100644 --- a/mengyastore-frontend/src/modules/store/ProductDetail.vue +++ b/mengyastore-frontend/src/modules/store/ProductDetail.vue @@ -68,6 +68,15 @@
+
@@ -91,6 +100,8 @@ import { computed, onMounted, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import MarkdownIt from 'markdown-it' import { fetchProducts, recordProductView } from '../shared/api' +import { isLoggedIn } from '../shared/auth' +import { isInWishlist, toggleWishlist } from '../shared/useWishlist' const route = useRoute() const router = useRouter() @@ -140,6 +151,12 @@ const galleryImages = computed(() => { return images }) +const loggedIn = computed(() => isLoggedIn()) +const inWishlist = computed(() => product.value ? isInWishlist(product.value.id) : false) +const onToggleWishlist = () => { + if (product.value) toggleWishlist(product.value.id) +} + const goBack = () => { router.push('/') } @@ -215,7 +232,7 @@ onMounted(async () => { .detail-description { display: block; color: var(--text); - font-size: 15px; + font-size: 17px; line-height: 1.8; -webkit-line-clamp: initial; -webkit-box-orient: initial; @@ -231,7 +248,7 @@ onMounted(async () => { .detail-gallery-main { position: relative; overflow: hidden; - border-radius: 18px; + border-radius: 10px; border: 1px solid var(--line); background: rgba(255, 255, 255, 0.45); } @@ -292,11 +309,11 @@ onMounted(async () => { width: 100%; height: 76px; object-fit: cover; - border-radius: 10px; + border-radius: 6px; } .detail-thumb span { - font-size: 12px; + font-size: 14px; color: var(--muted); } @@ -310,6 +327,16 @@ onMounted(async () => { font-weight: 900; } +.wishlist-action { + color: var(--muted); +} + +.wishlist-action.wishlist-active { + color: #e8826a; + border-color: rgba(232, 130, 106, 0.35); + background: rgba(255, 240, 235, 0.6); +} + @media (max-width: 900px) { .detail-thumb { padding: 6px; diff --git a/mengyastore-frontend/src/modules/store/StorePage.vue b/mengyastore-frontend/src/modules/store/StorePage.vue index 2af4c54..5a7fefe 100644 --- a/mengyastore-frontend/src/modules/store/StorePage.vue +++ b/mengyastore-frontend/src/modules/store/StorePage.vue @@ -1,91 +1,44 @@