Compare commits

..

81 Commits

Author SHA1 Message Date
shanshanzhong147 49d59eccb0 Merge old/master into main, resolving conflicts by preferring old in workflows and removing deprecated adapter files
Build docker and publish / build (20.15.1) (push) Has been cancelled
2025-12-28 15:56:07 -08:00
shanshanzhong147 c07c58aa16 x 2025-09-30 07:52:54 -07:00
shanshanzhong147 b650b1a108 tg
Build docker and publish / build (20.15.1) (push) Successful in 6m13s
2025-09-29 17:45:09 -07:00
shanshanzhong147 efc5be5142 maindev
Build docker and publish / build (20.15.1) (push) Failing after 6m30s
2025-09-29 17:32:10 -07:00
shanshanzhong147 5c43bd7a3d init
Build docker and publish / build (20.15.1) (push) Failing after 9m55s
2025-09-27 10:52:39 -07:00
shanshanzhong147 30bbb33129 init
Build docker and publish / build (20.15.1) (push) Failing after 6m3s
2025-09-26 21:49:16 -07:00
shanshanzhong147 66e753e69b x
Build docker and publish / build (20.15.1) (push) Failing after 6m2s
2025-09-26 21:04:11 -07:00
shanshanzhong147 93fcaace26 x
Build docker and publish / build (20.15.1) (push) Failing after 5m51s
2025-09-26 20:12:09 -07:00
shanshanzhong147 97bb873bb8 x
Build docker and publish / build (20.15.1) (push) Failing after 5m41s
2025-09-26 19:46:56 -07:00
shanshanzhong147 723ceddc3d x
Build docker and publish / build (20.15.1) (push) Failing after 5m24s
2025-09-26 19:34:49 -07:00
shanshanzhong147 be532bec1b x
Build docker and publish / build (20.15.1) (push) Failing after 5m23s
2025-09-26 19:25:47 -07:00
shanshanzhong147 e788693713 x
Build docker and publish / build (20.15.1) (push) Failing after 5m33s
2025-09-26 19:16:32 -07:00
shanshanzhong147 aaa6cb149c x
Build docker and publish / build (20.15.1) (push) Failing after 10m26s
2025-09-26 19:05:02 -07:00
shanshanzhong147 6a146f0898 x
Build docker and publish / build (20.15.1) (push) Failing after 1m1s
2025-09-26 19:00:51 -07:00
shanshanzhong147 b9b0dc3bda x
Build docker and publish / build (20.15.1) (push) Failing after 14s
2025-09-26 18:57:19 -07:00
shanshanzhong147 4d39fd1b84 i1
Build docker and publish / build (20.15.1) (push) Failing after 7s
2025-09-26 18:43:33 -07:00
shanshanzhong147 bcf81ab683 i1
Build docker and publish / build (push) Failing after 19s
2025-09-26 18:42:35 -07:00
shanshanzhong147 7471fd8e3d ci
Build docker and publish / linux (1.24.3) (push) Has been cancelled
2025-09-26 18:37:43 -07:00
shanshanzhong147 ac3bbaf7bf feat(ticket): 添加issue_type字段支持工单分类
在工单系统中新增issue_type字段用于区分工单类型(0-普通工单,1-提现工单)
修改相关API、模型和逻辑以支持该字段的创建、查询和筛选
2025-09-15 01:42:44 -07:00
shanshanzhong147 2d4bfe0330 remark 2025-09-14 23:41:00 -07:00
shanshanzhong147 602ecc31bc feat(proxy): 新增混合模式和中继直连模式支持
添加 RelayModeMixed、RelayModeAllDirect 和 RelayModeRandomDirect 三种中继模式
修复 README.md 中的拼写错误
更新 JWT 密钥和日志模式配置
优化流量日志查询排序
统一邮件验证码主题格式
2025-09-01 02:22:16 -07:00
shanshanzhong147 b16ac2cb9b feat(用户): 添加用户余额日志分页查询功能
修复AnyTLSUri端口使用错误问题
添加订阅列表排序功能
更新.gitignore移除日志文件忽略
完善流量通知模板变量
2025-08-27 08:15:31 -07:00
shanshanzhong147 41423ca666 fix: 修复订阅列表查询逻辑并更新Dockerfile配置
修复订阅列表查询中服务器组为空时的处理逻辑,避免潜在错误
在getSubscribeListLogic.go中添加服务器数量统计功能
更新Dockerfile添加logs目录拷贝
清理ppanel.yaml配置文件中的合并冲突和冗余配置
2025-08-17 04:28:32 -07:00
shanshanzhong147 e898e6afbe init 2025-08-12 07:52:04 -07:00
shanshanzhong147 2e01ba0e3d ci(docker): 更新Docker工作流和配置
清理Go模块缓存并移除go.sum文件
更新Docker网络名称和数据库连接配置
2025-08-10 00:28:32 -07:00
shanshanzhong147 25db7ea428 chore(deploy): 更新GITEA_RUNNER_NAME为ubuntu-latest 2025-08-09 08:32:09 -07:00
shanshanzhong147 e37ad50410 ci: 将linux作业的运行环境从vpn改为ubuntu-latest 2025-08-09 08:17:36 -07:00
shanshanzhong147 c503e06c9c ci(workflow): 将linux作业的运行环境改为vpn
避免使用公共runner以提高构建安全性
2025-08-09 08:00:12 -07:00
shanshanzhong147 0992c2ad7b ci(docker): 更新Docker工作流以包含构建步骤
添加checkout步骤并使用环境变量进行认证
包含构建Linux可执行文件的步骤
2025-08-09 07:18:58 -07:00
shanshanzhong147 e78b93d6fb ci: 移除不必要的检出步骤 2025-08-09 07:13:25 -07:00
shanshanzhong147 79020ccea0 1 2025-08-09 06:55:11 -07:00
shanshanzhong147 8a8ece76b1 ci: 将Linux构建任务运行环境从vpn改为ubuntu-latest 2025-08-09 06:26:21 -07:00
shanshanzhong147 0316345e9f ci: 将linux job的运行环境从vpn-runner改为vpn 2025-08-09 06:25:23 -07:00
shanshanzhong147 b56b1240f3 ci(workflow): 将linux job的运行环境改为vpn-runner并移除build-linux步骤 2025-08-09 06:23:30 -07:00
shanshanzhong147 77b3af8015 init 2025-08-09 05:18:44 -07:00
shanshanzhong147 634f43d644 fix: 更新MySQL和Redis的连接地址为具体IP
将MySQL和Redis的连接地址从服务名称改为具体IP地址,以便在特定网络环境下能够正确连接
2025-08-05 10:37:47 -07:00
shanshanzhong147 e5b4dc75d7 fix(deploy): 更新docker-compose配置和数据库连接地址
- 在docker-compose中添加时区环境变量
- 将MySQL和Redis连接地址从localhost改为服务名
- 更新Makefile中的docker compose命令语法
2025-08-05 10:12:29 -07:00
shanshanzhong147 40b8c07036 chore: 在docker-run命令中添加当前工作目录打印 2025-08-05 10:00:07 -07:00
shanshanzhong147 b261ddeeb1 ci(docker): 更新Docker工作流,添加构建步骤并注释发布步骤
添加应用构建步骤以确保镜像包含最新代码
暂时注释发布步骤以进行本地测试
2025-08-05 09:52:27 -07:00
shanshanzhong147 62f07b7f6b ci(docker): 移除构建应用的步骤并修复docker-compose命令
移除docker.yml中冗余的go mod tidy和build-linux步骤,这些步骤已在其他流程中处理
将docker compose命令改为docker-compose以兼容旧版本
2025-08-05 09:46:27 -07:00
shanshanzhong147 31a2aa0e84 ci(workflow): 优化Docker工作流配置并简化步骤
- 添加DOMAIN环境变量以替代硬编码的域名
- 简化git克隆流程,使用凭证存储提高安全性
- 移除不必要的SSH文件复制步骤
- 添加构建和发布Docker镜像的详细日志输出
2025-08-05 09:24:13 -07:00
shanshanzhong147 7965c33790 ci(docker): 更新Gitea工作流以使用容器化构建环境
将Linux作业改为在golang容器中运行,简化Go环境设置步骤
移除不必要的SSH步骤,优化构建流程
2025-08-05 09:22:37 -07:00
shanshanzhong147 fcae874fd2 ci: 移除不再需要的Node.js安装步骤 2025-08-05 09:20:50 -07:00
shanshanzhong147 dad83339c1 ci(docker): 更新Gitea工作流配置并优化构建步骤
- 使用标准actions/checkout@v4替代自定义checkout
- 添加Go环境设置步骤并启用缓存
- 优化SSH action使用标准appleboy/ssh-action
- 调整工作空间路径变量为github标准
2025-08-05 09:18:32 -07:00
shanshanzhong147 84842ec3dd ci(docker): 添加 Node.js 设置步骤以支持前端构建 2025-08-05 09:16:06 -07:00
shanshanzhong147 59bc008b69 ci(docker): 更新SSH action的版本引用
使用明确的版本号v1替代隐式引用,提高工作流的稳定性
2025-08-05 09:13:06 -07:00
shanshanzhong147 8cb58ad91f ci(docker): 更新Gitea Actions checkout步骤使用v1版本
修复checkout步骤未指定版本的问题,确保构建稳定性
2025-08-05 09:12:16 -07:00
shanshanzhong147 7372302618 ci(docker): 移除actions引用中的main分支后缀 2025-08-05 09:10:44 -07:00
shanshanzhong147 cbf5a2b450 1 2025-08-05 09:09:02 -07:00
shanshanzhong147 33f450cdbc init 2025-08-05 08:47:18 -07:00
shanshanzhong147 bce4c4e56e ci(workflow): 更新Gitea工作流使用内部actions
使用内部认证的actions替换公开actions以增强安全性
2025-08-05 08:22:04 -07:00
shanshanzhong147 b6cbcb3040 ci: 移除不必要的Node.js设置和检出步骤 2025-08-05 08:09:59 -07:00
shanshanzhong147 fdbe3c75ee ci(workflow): 添加Node.js环境并降级checkout action版本
添加Node.js环境设置步骤以满足构建需求
将actions/checkout从v4降级至v3以解决兼容性问题
2025-08-05 08:08:36 -07:00
shanshanzhong147 bf13c56261 ci: 更新Gitea工作流中使用GitHub官方actions
将自定义的actions地址替换为GitHub官方actions,提高工作流的稳定性和可维护性
2025-08-05 08:05:21 -07:00
shanshanzhong147 85343f3f82 fix(deploy): 修正GITEA_RUNNER_NAME变量值为vpn-runner 2025-08-05 08:01:25 -07:00
shanshanzhong147 51b493a10d ci(workflow): 将linux作业的运行环境从ubuntu-latest改为vpn 2025-08-05 07:59:50 -07:00
shanshanzhong147 ae04e0d518 chore: 更新GITEA_RUNNER_NAME为vpn-runner 2025-08-05 07:54:18 -07:00
shanshanzhong147 2b1ce740e1 ci: 更新部署配置和CI认证方式
将域名从ppanel.kxsw.us更改为api.kxsw.us
将CI工作流中的SSH认证方式从私钥改为密码认证
2025-08-05 07:47:50 -07:00
shanshanzhong147 e620abcce6 init 2025-08-05 06:19:25 -07:00
shanshanzhong147 7ea6fc226e refactor(Dockerfile): 优化时区文件拷贝并添加注释
将单个时区文件拷贝改为拷贝整个时区目录,提高可维护性
添加时区设置的环境变量注释
2025-07-28 06:56:07 -07:00
shanshanzhong147 d5f4b04c6c fix(deploy): 将API外部端口从8070改为8080 2025-07-24 09:33:40 -07:00
shanshanzhong147 a0ca976e66 chore: 开启调试模式并添加代码对比脚本
开启调试模式以便更好地排查问题。
添加代码对比脚本 see.sh,用于简洁地对比代码变更,包括文件变更对比和变更统计,方便查看我们和他们的修改情况。
2025-07-23 10:13:42 -07:00
shanshanzhong147 917732eeee fix: 修改 MySQL 配置中的密码和数据库名 2025-07-23 09:43:38 -07:00
shanshanzhong147 f0fbd9d208 fix: 更新MySQL和Redis连接地址以及Docker网络名称
将MySQL地址从localhost改为mysql_db,Redis地址从127.0.0.1改为redis_cache
更新Docker网络名称为ppanel-script_ppanel-network以匹配部署配置
2025-07-23 09:25:32 -07:00
shanshanzhong147 2d07182bd6 fix: 修正 Dockerfile 中的 CMD 命令参数 2025-07-23 09:20:32 -07:00
shanshanzhong147 e0ae15355b style: 将项目名称从Ppanel改为ppanel以保持命名一致性 2025-07-23 08:48:36 -07:00
shanshanzhong147 c0e0d03431 fix: 修正Dockerfile和Makefile中的可执行文件名称
将Dockerfile中的CMD命令和Makefile中的build-linux目标输出文件名称统一为'ppanel',确保容器运行时使用正确的可执行文件名称
2025-07-23 08:22:45 -07:00
shanshanzhong147 bb5d6974fa ci: 从发布流程中移除docker登录步骤
移除Makefile中自动登录docker的步骤,因为登录凭证应通过更安全的方式处理
2025-07-23 07:51:46 -07:00
shanshanzhong147 df7fcddb40 refactor: 更新Dockerfile和Makefile配置
统一服务名称为Ppanel并更新相关变量命名
优化Makefile结构,移除无用swagger相关命令
添加构建时间变量并改进构建命令
2025-07-23 07:36:26 -07:00
shanshanzhong147 6c9d5fb39e fix: 修正Makefile中的服务名称拼写错误 2025-07-22 10:25:41 -07:00
shanshanzhong147 4ecc5177ed ci(docker): 优化Go环境设置并启用缓存
使用setup-go替代gitea-tool-cache来设置Go环境
启用依赖缓存以提高构建效率
移除不必要的清理步骤
2025-07-22 10:11:10 -07:00
shanshanzhong147 c11d46bdbf ci: 更新工作流中使用的工作空间变量从github到gitea 2025-07-22 10:09:55 -07:00
shanshanzhong147 9b7a588997 ci(workflow): 更新docker工作流配置
- 添加GOSUMDB=off设置并清理go缓存
- 修复工作空间路径变量引用错误
- 增加go.sum清理步骤
2025-07-22 10:05:09 -07:00
shanshanzhong147 0f40c63ea0 ci(docker): 修复工作流中容器环境变量处理逻辑
修复容器名称变量引用错误,并添加对RUNNER_CONTAINER_NAME未设置的检查
2025-07-22 09:53:59 -07:00
shanshanzhong147 1d1d8c0e30 ci(workflows): 修改docker工作流中的环境文件路径
将环境文件存储路径从/install/cicd_env_files改为用户主目录下的~/cicd_env_files,避免权限问题
2025-07-22 09:52:25 -07:00
shanshanzhong147 5d890fdfcc ci: 更新Gitea Actions的引用版本为main分支 2025-07-22 09:50:20 -07:00
shanshanzhong147 94e2f33db1 ci: 移除Gitea Actions插件版本号以使用最新版本 2025-07-22 09:45:30 -07:00
shanshanzhong147 a7d9deb9cd ci(workflow): 更新 setup-go action 的引用方式 2025-07-22 09:36:18 -07:00
shanshanzhong147 9f9e832e37 chore(deploy): 更新GITEA_RUNNER_NAME为server-runner 2025-07-22 09:08:59 -07:00
shanshanzhong147 a73a3f2313 feat: 添加docker-compose配置和用户管理逻辑更新
fix(ci): 移除敏感信息并更新SSH认证方式

refactor: 清理无用文件和更新Makefile配置

docs: 添加项目配置文件和更新环境变量说明
2025-07-22 08:53:30 -07:00
shanshanzhong147 cc6ebc18e5 ci(docker): 添加docker构建工作流并更新Makefile配置
添加Gitea的docker构建工作流,包含环境变量配置、Go环境安装、镜像构建和发布步骤
重构Makefile以支持多平台构建、docker操作和swagger文档生成
2025-07-21 07:25:47 -07:00
159 changed files with 764 additions and 3684 deletions
+231
View File
@@ -0,0 +1,231 @@
name: Build docker and publish
run-name: 简化的Docker构建和部署流程
on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
env:
# Docker镜像仓库
REPO: ${{ vars.REPO || 'registry.kxsw.us/ppanel-server' }}
# SSH连接信息
SSH_HOST: ${{ vars.SSH_HOST }}
SSH_PORT: ${{ vars.SSH_PORT }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_PASSWORD: ${{ vars.SSH_PASSWORD }}
# TG通知
TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0
TG_CHAT_ID: "-4940243803"
# Go构建变量
SERVICE: ppanel
SERVICE_STYLE: ppanel
VERSION: ${{ github.sha }}
BUILDTIME: ${{ github.event.head_commit.timestamp }}
GOARCH: amd64
jobs:
build:
runs-on: ppanel-server
container:
image: node:20
strategy:
matrix:
# 只有node支持版本号别名
node: ['20.15.1']
steps:
# 步骤1: 下载代码
- name: 📥 下载代码
uses: actions/checkout@v4
# 步骤2: 设置动态环境变量
- name: ⚙️ 设置动态环境变量
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
echo "DEPLOY_PATH=/root/vpn_server" >> $GITHUB_ENV
echo "为 main 分支设置生产环境变量"
elif [ "${{ github.ref_name }}" = "dev" ]; then
echo "DOCKER_TAG_SUFFIX=dev" >> $GITHUB_ENV
echo "CONTAINER_NAME=ppanel-server-dev" >> $GITHUB_ENV
echo "DEPLOY_PATH=/root/vpn_server_dev" >> $GITHUB_ENV
echo "为 dev 分支设置开发环境变量"
else
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
echo "CONTAINER_NAME=ppanel-server-${{ github.ref_name }}" >> $GITHUB_ENV
echo "DEPLOY_PATH=/root/vpn_server_other" >> $GITHUB_ENV
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
fi
# 步骤3: 安装系统工具 (Docker, curl, jq)
- name: 🔧 安装系统工具 (Docker, curl, jq)
run: |
set -e
export DEBIAN_FRONTEND=noninteractive
echo "等待 apt/dpkg 锁释放 (unattended-upgrades)..."
# 等待最多 300 秒让 unattended-upgrades/apt/dpkg 锁释放
end=$((SECONDS+300))
while true; do
LOCKS_BUSY=0
# 如果 unattended-upgrades 正在运行,标记为忙碌
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
# 如果 fuser 存在,检查常见的锁文件
if command -v fuser >/dev/null 2>&1; then
if fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
|| fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|| fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then
LOCKS_BUSY=1
fi
fi
# 如果不忙碌则跳出循环
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
# 超时后约 5 分钟
if [ $SECONDS -ge $end ]; then
echo "等待 apt/dpkg 锁超时,使用 Dpkg::Lock::Timeout 继续..."
break
fi
echo "仍在等待锁释放..."; sleep 5
done
apt-get update -y -o Dpkg::Lock::Timeout=600
apt-get install -y -o Dpkg::Lock::Timeout=600 jq curl ca-certificates docker.io
docker --version
jq --version
curl --version
# 步骤4: 构建并发布到镜像仓库
- name: 📤 构建并发布到镜像仓库
run: |
echo "开始构建并推送镜像..."
echo "仓库: ${{ env.REPO }}"
echo "版本标签: ${{ env.VERSION }}"
echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}"
# 构建镜像,同时打上版本和分支两个标签
docker build -f Dockerfile \
--platform linux/amd64 \
--build-arg TARGETARCH=amd64 \
--build-arg VERSION=${{ env.VERSION }} \
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
-t ${{ env.REPO }}:${{ env.VERSION }} \
-t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} \
.
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
docker push ${{ env.REPO }}:${{ env.VERSION }}
echo "推送分支标签镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
docker push ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
echo "镜像推送完成"
# 步骤5: 连接服务器拉镜像启动
- name: 🚀 连接服务器拉镜像启动
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SSH_HOST }}
username: ${{ env.SSH_USER }}
password: ${{ env.SSH_PASSWORD }}
port: ${{ env.SSH_PORT }}
timeout: 300s
command_timeout: 600s
script: |
echo "连接服务器成功,开始部署..."
echo "部署容器名: ${{ env.CONTAINER_NAME }}"
echo "部署路径: ${{ env.DEPLOY_PATH }}"
echo "部署镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
# 确保部署目录存在
mkdir -p ${{ env.DEPLOY_PATH }}/config
mkdir -p ${{ env.DEPLOY_PATH }}/logs
# 停止并删除旧容器(忽略所有错误)
if docker ps -a | grep -q ${{ env.CONTAINER_NAME }} 2>/dev/null; then
echo "停止旧容器..."
docker stop ${{ env.CONTAINER_NAME }} >/dev/null 2>&1 || true
echo "等待容器完全停止..."
sleep 5
echo "删除旧容器..."
# 静默删除,完全忽略错误输出
docker rm ${{ env.CONTAINER_NAME }} >/dev/null 2>&1 || true
sleep 2
# 如果仍然存在,尝试强制删除(静默)
if docker ps -a | grep -q ${{ env.CONTAINER_NAME }} 2>/dev/null; then
echo "尝试强制删除..."
docker rm -f ${{ env.CONTAINER_NAME }} >/dev/null 2>&1 || true
sleep 3
fi
echo "容器清理完成,继续部署..."
fi
# 拉取最新分支镜像
echo "拉取镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}..."
docker pull ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
# 启动新容器
echo "启动新容器..."
cd ${{ env.DEPLOY_PATH }}
docker run -d \
--name ${{ env.CONTAINER_NAME }} \
--restart unless-stopped \
--network host \
-v ./config/ppanel.yaml:/app/etc/ppanel.yaml \
-v ./logs:/app/logs \
${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
# 检查容器状态
sleep 5
if docker ps | grep -q ${{ env.CONTAINER_NAME }}; then
echo "✅ 容器启动成功"
else
echo "❌ 容器启动失败"
docker logs ${{ env.CONTAINER_NAME }}
exit 1
fi
# 步骤6: TG通知 (成功)
- name: 📱 发送成功通知到Telegram
if: success()
uses: appleboy/telegram-action@master
with:
token: ${{ env.TG_BOT_TOKEN }}
to: ${{ env.TG_CHAT_ID }}
message: |
✅ 部署成功!
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
📝 提交: ${{ github.sha }}
👤 提交者: ${{ github.actor }}
🕐 时间: ${{ github.event.head_commit.timestamp }}
🚀 服务已成功部署到生产环境
parse_mode: Markdown
# 步骤5: TG通知 (失败)
- name: 📱 发送失败通知到Telegram
if: failure()
uses: appleboy/telegram-action@master
with:
token: ${{ env.TG_BOT_TOKEN }}
to: ${{ env.TG_CHAT_ID }}
message: |
❌ 部署失败!
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
📝 提交: ${{ github.sha }}
👤 提交者: ${{ github.actor }}
🕐 时间: ${{ github.event.head_commit.timestamp }}
⚠️ 请检查构建日志获取详细信息
parse_mode: Markdown
-131
View File
@@ -1,131 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
build-docker:
runs-on: ubuntu-latest
env:
IMAGE_NAME: ppanel-server
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract version from git tag
id: version
run: echo "VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_ENV
- name: Get short SHA
id: sha
run: echo "GIT_SHA=${GITHUB_SHA::8}" >> $GITHUB_ENV
- name: Set BUILD_TIME env
run: echo BUILD_TIME=$(date --iso-8601=seconds) >> ${GITHUB_ENV}
- name: Build and push Docker image for main release
if: "!contains(github.ref_name, 'beta')"
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
VERSION=${{ env.VERSION }}
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}-${{ env.GIT_SHA }}
- name: Build and push Docker image for beta release
if: contains(github.ref_name, 'beta')
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
VERSION=${{ env.VERSION }}
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:beta
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}-${{ env.GIT_SHA }}
release-notes:
runs-on: ubuntu-latest
needs: build-docker
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Install GoReleaser
run: |
go install github.com/goreleaser/goreleaser/v2@latest
- name: Run GoReleaser
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
goreleaser check
goreleaser release --clean
releases-matrix:
name: Release ppanel-server binary
runs-on: ubuntu-latest
needs: release-notes # wait for release-notes job to finish
strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64,
# windows/386, windows/amd64, windows/arm64, darwin/amd64, darwin/arm64
goos: [ linux, windows, darwin ]
goarch: [ '386', amd64, arm64 ]
exclude:
- goarch: '386'
goos: darwin
steps:
- uses: actions/checkout@v2
- name: Extract version from git tag
id: version
run: echo "VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_ENV
- name: Set BUILD_TIME env
run: echo BUILD_TIME=$(date --iso-8601=seconds) >> ${GITHUB_ENV}
- uses: actions/checkout@v4
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
asset_name: "ppanel-server-${{ matrix.goos }}-${{ matrix.goarch }}"
goversion: "https://dl.google.com/go/go1.23.3.linux-amd64.tar.gz"
project_path: "."
binary_name: "ppanel-server"
extra_files: LICENSE etc
ldflags: -X "github.com/perfect-panel/server/pkg/constant.Version=${{env.VERSION}}" -X "github.com/perfect-panel/server/pkg/constant.BuildTime=${{env.BUILD_TIME}}"
-2
View File
@@ -3,10 +3,8 @@
*-dev.yaml *-dev.yaml
*.local.yaml *.local.yaml
/test/ /test/
*.log
.DS_Store .DS_Store
*_test_config.go *_test_config.go
*.log*
/build/ /build/
*.p8 *.p8
*.crt *.crt
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="go build github.com/perfect-panel/server" type="GoApplicationRunConfiguration" factoryName="Go Application" nameIsGenerated="true">
<module name="server" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="run --config etc/ppanel-dev.yaml" />
<kind value="PACKAGE" />
<package value="github.com/perfect-panel/server" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/ppanel.go" />
<method v="2" />
</configuration>
</component>
+4 -2
View File
@@ -24,12 +24,13 @@ RUN BUILD_TIME=$(date -u +"%Y-%m-%d %H:%M:%S") && \
go build -ldflags="-s -w -X 'github.com/perfect-panel/server/pkg/constant.Version=${VERSION}' -X 'github.com/perfect-panel/server/pkg/constant.BuildTime=${BUILD_TIME}'" -o /app/ppanel ppanel.go go build -ldflags="-s -w -X 'github.com/perfect-panel/server/pkg/constant.Version=${VERSION}' -X 'github.com/perfect-panel/server/pkg/constant.BuildTime=${BUILD_TIME}'" -o /app/ppanel ppanel.go
# Final minimal image # Final minimal image
FROM scratch FROM alpine:latest
# Copy CA certificates and timezone data # Copy CA certificates and timezone data
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Set Shanghai timezone
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
# Set working directory and copy binary # Set working directory and copy binary
@@ -37,6 +38,7 @@ WORKDIR /app
COPY --from=builder /app/ppanel /app/ppanel COPY --from=builder /app/ppanel /app/ppanel
COPY --from=builder /build/etc /app/etc COPY --from=builder /build/etc /app/etc
COPY --from=builder /build/logs /app/logs
# Expose the port (optional) # Expose the port (optional)
EXPOSE 8080 EXPOSE 8080
+66 -77
View File
@@ -1,84 +1,73 @@
NAME="ppanel-server" # Custom configuration | 独立配置
BINDIR=bin # Service name | 项目名称
VERSION=$(shell git describe --tags || echo "unknown version") SERVICE=ppanel
BUILDTIME=$(shell date -u) # Service name in specific style | 项目经过style格式化的名称
SERVICE_STYLE=ppanel
# Service name in lowercase | 项目名称全小写格式
SERVICE_LOWER=ppanel
# Service name in snake format | 项目名称下划线格式
SERVICE_SNAKE=ppanel
# Service name in snake format | 项目名称短杠格式
SERVICE_DASH=ppanel
# The project version, if you don't use git, you should set it manually | 项目版本,如果不使用git请手动设置
VERSION=$(shell git describe --tags --always)
# Build time | 构建时间
BUILDTIME=$(shell date '+%Y-%m-%d %H:%M:%S')
# The project file name style | 项目文件命名风格
PROJECT_STYLE=go_zero
# Whether to use i18n | 是否启用 i18n
PROJECT_I18N=true
# The suffix after build or compile | 构建后缀
PROJECT_BUILD_SUFFIX=api
# The arch of the build | 构建的架构
GOARCH=amd64
# The repository of docker | Docker 仓库地址
DOCKER_REPO=docker.i.on ticcheck
# ---- You may not need to modify the codes below | 下面的代码大概率不需要更改 ----
GO ?= go
GOFMT ?= gofmt "-s"
GOFILES := $(shell find . -name "*.go")
LDFLAGS := -s -w
# Build command with version injection | 带版本信息注入的构建命令
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/perfect-panel/server/pkg/constant.Version=$(VERSION)" \ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/perfect-panel/server/pkg/constant.Version=$(VERSION)" \
-X "github.com/perfect-panel/server/pkg/constant.BuildTime=$(BUILDTIME)" \ -X "github.com/perfect-panel/server/pkg/constant.BuildTime=$(BUILDTIME)" \
-w -s -buildid=' -w -s -buildid='
PLATFORM_LIST = \ .PHONY: docker
darwin-amd64 \ docker: # Build the docker image | 构建 docker 镜像
darwin-amd64-v3 \ docker build -f Dockerfile -t ${API_IMAGE_NAME}:${VERSION} .
darwin-arm64 \ @echo "Build docker successfully"
linux-386 \
linux-amd64 \
linux-amd64-v3 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-arm64 \
WINDOWS_ARCH_LIST = \ .PHONY: publish-docker
windows-386 \ publish-docker: # Publish docker image | 发布 docker 镜像
windows-amd64 \ docker tag ${API_IMAGE_NAME}:${VERSION} ${API_IMAGE_NAME}:latest
windows-amd64-v3 \ docker push ${API_IMAGE_NAME}:latest
windows-arm64 \ @echo "Publish docker successfully"
windows-armv7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used .PHONY: docker-run
docker-run: # Publish docker image | 发布 docker 镜像
darwin-amd64: docker rm -f ${API_CONTAINER_NAME}
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ @echo "pwd: $(pwd)"
docker compose -p ${DOCKER_PROJECT_NAME} -f ${DOCKER_COMPOSE_FILE} up -d
darwin-amd64-v3: @echo "docker run successfully"
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-v3:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-v3:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-armv7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) .PHONY: build-linux
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) build-linux: # Build project for Linux | 构建Linux下的可执行文件
GOOS=linux GOARCH=$(GOARCH) $(GOBUILD) -o /app/ppanel $(SERVICE_STYLE).go
@echo "Build project for Linux successfully"
$(gz_releases): %.gz : % .PHONY: help
chmod +x $(BINDIR)/$(NAME)-$(basename $@) help: # Show help | 显示帮助
gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) @grep kfile | sort | while read-r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done
$(zip_releases): %.zip : %
zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe
+3 -3
View File
@@ -113,9 +113,9 @@ proxy services. Built with Go, it emphasizes performance, security, and scalabil
``` ```
4. **Pull from Docker Hub** (after CI/CD publishes): 4. **Pull from Docker Hub** (after CI/CD publishes):
```bash ```bashw
docker pull ppanel/ppanel-server:latest docker pull yourusername/ppanel-server:latest
docker run --rm -p 8080:8080 ppanel/ppanel-server:latest docker run --rm -p 8080:8080 yourusername/ppanel-server:latest
``` ```
## 📖 API Documentation ## 📖 API Documentation
+29 -47
View File
@@ -102,53 +102,35 @@ func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) {
for _, protocol := range protocols { for _, protocol := range protocols {
if protocol.Type == item.Protocol { if protocol.Type == item.Protocol {
proxies = append(proxies, Proxy{ proxies = append(proxies, Proxy{
Sort: item.Sort, Sort: item.Sort,
Name: item.Name, Name: item.Name,
Server: item.Address, Server: item.Address,
Port: item.Port, Port: item.Port,
Type: item.Protocol, Type: item.Protocol,
Tags: strings.Split(item.Tags, ","), Tags: strings.Split(item.Tags, ","),
Security: protocol.Security, Security: protocol.Security,
SNI: protocol.SNI, SNI: protocol.SNI,
AllowInsecure: protocol.AllowInsecure, AllowInsecure: protocol.AllowInsecure,
Fingerprint: protocol.Fingerprint, Fingerprint: protocol.Fingerprint,
RealityServerAddr: protocol.RealityServerAddr, RealityServerAddr: protocol.RealityServerAddr,
RealityServerPort: protocol.RealityServerPort, RealityServerPort: protocol.RealityServerPort,
RealityPrivateKey: protocol.RealityPrivateKey, RealityPrivateKey: protocol.RealityPrivateKey,
RealityPublicKey: protocol.RealityPublicKey, RealityPublicKey: protocol.RealityPublicKey,
RealityShortId: protocol.RealityShortId, RealityShortId: protocol.RealityShortId,
Transport: protocol.Transport, Transport: protocol.Transport,
Host: protocol.Host, Host: protocol.Host,
Path: protocol.Path, Path: protocol.Path,
ServiceName: protocol.ServiceName, ServiceName: protocol.ServiceName,
Method: protocol.Cipher, Method: protocol.Cipher,
ServerKey: protocol.ServerKey, ServerKey: protocol.ServerKey,
Flow: protocol.Flow, Flow: protocol.Flow,
HopPorts: protocol.HopPorts, HopPorts: protocol.HopPorts,
HopInterval: protocol.HopInterval, HopInterval: protocol.HopInterval,
ObfsPassword: protocol.ObfsPassword, ObfsPassword: protocol.ObfsPassword,
UpMbps: protocol.UpMbps, DisableSNI: protocol.DisableSNI,
DownMbps: protocol.DownMbps, ReduceRtt: protocol.ReduceRtt,
DisableSNI: protocol.DisableSNI, UDPRelayMode: protocol.UDPRelayMode,
ReduceRtt: protocol.ReduceRtt, CongestionController: protocol.CongestionController,
UDPRelayMode: protocol.UDPRelayMode,
CongestionController: protocol.CongestionController,
PaddingScheme: protocol.PaddingScheme,
Multiplex: protocol.Multiplex,
XhttpMode: protocol.XhttpMode,
XhttpExtra: protocol.XhttpExtra,
Encryption: protocol.Encryption,
EncryptionMode: protocol.EncryptionMode,
EncryptionRtt: protocol.EncryptionRtt,
EncryptionTicket: protocol.EncryptionTicket,
EncryptionServerPadding: protocol.EncryptionServerPadding,
EncryptionPrivateKey: protocol.EncryptionPrivateKey,
EncryptionClientPadding: protocol.EncryptionClientPadding,
EncryptionPassword: protocol.EncryptionPassword,
Ratio: protocol.Ratio,
CertMode: protocol.CertMode,
CertDNSProvider: protocol.CertDNSProvider,
CertDNSEnv: protocol.CertDNSEnv,
}) })
} }
} }
-7
View File
@@ -102,9 +102,6 @@ type (
BatchDeleteSubscribeRequest { BatchDeleteSubscribeRequest {
Ids []int64 `json:"ids" validate:"required"` Ids []int64 `json:"ids" validate:"required"`
} }
ResetAllSubscribeTokenResponse {
Success bool `json:"success"`
}
) )
@server ( @server (
@@ -160,9 +157,5 @@ service ppanel {
@doc "Subscribe sort" @doc "Subscribe sort"
@handler SubscribeSort @handler SubscribeSort
post /sort (SubscribeSortRequest) post /sort (SubscribeSortRequest)
@doc "Reset all subscribe tokens"
@handler ResetAllSubscribeToken
post /reset_all_token returns (ResetAllSubscribeTokenResponse)
} }
+2 -11
View File
@@ -19,13 +19,8 @@ type (
Periods []TimePeriod `json:"periods"` Periods []TimePeriod `json:"periods"`
} }
PreViewNodeMultiplierResponse { PreViewNodeMultiplierResponse {
CurrentTime string `json:"current_time"` CurrentTime string `json:"current_time"`
Ratio float32 `json:"ratio"` Ratio float32 `json:"ratio"`
}
ModuleConfig {
Secret string `json:"secret"` // 通讯密钥
ServiceName string `json:"service_name"` // 服务名称
ServiceVersion string `json:"service_version"` // 服务版本
} }
) )
@@ -130,9 +125,5 @@ service ppanel {
@doc "PreView Node Multiplier" @doc "PreView Node Multiplier"
@handler PreViewNodeMultiplier @handler PreViewNodeMultiplier
get /node_multiplier/preview returns (PreViewNodeMultiplierResponse) get /node_multiplier/preview returns (PreViewNodeMultiplierResponse)
@doc "Get Module Config"
@handler GetModuleConfig
get /module returns (ModuleConfig)
} }
+6 -5
View File
@@ -16,11 +16,12 @@ type (
Status *uint8 `json:"status" validate:"required"` Status *uint8 `json:"status" validate:"required"`
} }
GetTicketListRequest { GetTicketListRequest {
Page int64 `form:"page"` Page int64 `form:"page"`
Size int64 `form:"size"` Size int64 `form:"size"`
UserId int64 `form:"user_id,omitempty"` UserId int64 `form:"user_id,omitempty"`
Status *uint8 `form:"status,omitempty"` Status *uint8 `form:"status,omitempty"`
Search string `form:"search,omitempty"` IssueType *uint8 `form:"issue_type,omitempty"`
Search string `form:"search,omitempty"`
} }
GetTicketListResponse { GetTicketListResponse {
Total int64 `json:"total"` Total int64 `json:"total"`
-12
View File
@@ -17,14 +17,6 @@ type (
VersionResponse { VersionResponse {
Version string `json:"version"` Version string `json:"version"`
} }
QueryIPLocationRequest {
IP string `form:"ip" validate:"required"`
}
QueryIPLocationResponse {
Country string `json:"country"`
Region string `json:"region,omitempty"`
City string `json:"city"`
}
) )
@server ( @server (
@@ -44,9 +36,5 @@ service ppanel {
@doc "Get Version" @doc "Get Version"
@handler GetVersion @handler GetVersion
get /version returns (VersionResponse) get /version returns (VersionResponse)
@doc "Query IP Location"
@handler QueryIPLocation
get /ip/location (QueryIPLocationRequest) returns (QueryIPLocationResponse)
} }
+20 -43
View File
@@ -11,13 +11,11 @@ info (
type ( type (
// User login request // User login request
UserLoginRequest { UserLoginRequest {
Identifier string `json:"identifier"` Email string `json:"email" validate:"required"`
Email string `json:"email" validate:"required"` Password string `json:"password" validate:"required"`
Password string `json:"password" validate:"required"` IP string `header:"X-Original-Forwarded-For"`
IP string `header:"X-Original-Forwarded-For"` UserAgent string `header:"User-Agent"`
UserAgent string `header:"User-Agent"` CfToken string `json:"cf_token,optional"`
LoginType string `header:"Login-Type"`
CfToken string `json:"cf_token,optional"`
} }
// Check user is exist request // Check user is exist request
CheckUserRequest { CheckUserRequest {
@@ -29,26 +27,22 @@ type (
} }
// User login response // User login response
UserRegisterRequest { UserRegisterRequest {
Identifier string `json:"identifier"` Email string `json:"email" validate:"required"`
Email string `json:"email" validate:"required"` Password string `json:"password" validate:"required"`
Password string `json:"password" validate:"required"` Invite string `json:"invite,optional"`
Invite string `json:"invite,optional"` Code string `json:"code,optional"`
Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"`
IP string `header:"X-Original-Forwarded-For"` UserAgent string `header:"User-Agent"`
UserAgent string `header:"User-Agent"` CfToken string `json:"cf_token,optional"`
LoginType string `header:"Login-Type"`
CfToken string `json:"cf_token,optional"`
} }
// User login response // User login response
ResetPasswordRequest { ResetPasswordRequest {
Identifier string `json:"identifier"` Email string `json:"email" validate:"required"`
Email string `json:"email" validate:"required"` Password string `json:"password" validate:"required"`
Password string `json:"password" validate:"required"` Code string `json:"code,optional"`
Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"`
IP string `header:"X-Original-Forwarded-For"` UserAgent string `header:"User-Agent"`
UserAgent string `header:"User-Agent"` CfToken string `json:"cf_token,optional"`
LoginType string `header:"Login-Type"`
CfToken string `json:"cf_token,optional"`
} }
LoginResponse { LoginResponse {
Token string `json:"token"` Token string `json:"token"`
@@ -66,14 +60,12 @@ type (
} }
// login request // login request
TelephoneLoginRequest { TelephoneLoginRequest {
Identifier string `json:"identifier"`
Telephone string `json:"telephone" validate:"required"` Telephone string `json:"telephone" validate:"required"`
TelephoneCode string `json:"telephone_code"` TelephoneCode string `json:"telephone_code"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password"` Password string `json:"password"`
IP string `header:"X-Original-Forwarded-For"` IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"` UserAgent string `header:"User-Agent"`
LoginType string `header:"Login-Type"`
CfToken string `json:"cf_token,optional"` CfToken string `json:"cf_token,optional"`
} }
// Check user is exist request // Check user is exist request
@@ -87,7 +79,6 @@ type (
} }
// User login response // User login response
TelephoneRegisterRequest { TelephoneRegisterRequest {
Identifier string `json:"identifier"`
Telephone string `json:"telephone" validate:"required"` Telephone string `json:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
@@ -95,19 +86,16 @@ type (
Code string `json:"code,optional"` Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"` IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"` UserAgent string `header:"User-Agent"`
LoginType string `header:"Login-Type,optional"`
CfToken string `json:"cf_token,optional"` CfToken string `json:"cf_token,optional"`
} }
// User login response // User login response
TelephoneResetPasswordRequest { TelephoneResetPasswordRequest {
Identifier string `json:"identifier"`
Telephone string `json:"telephone" validate:"required"` Telephone string `json:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
Code string `json:"code,optional"` Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"` IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"` UserAgent string `header:"User-Agent"`
LoginType string `header:"Login-Type,optional"`
CfToken string `json:"cf_token,optional"` CfToken string `json:"cf_token,optional"`
} }
AppleLoginCallbackRequest { AppleLoginCallbackRequest {
@@ -119,18 +107,11 @@ type (
Code string `form:"code"` Code string `form:"code"`
State string `form:"state"` State string `form:"state"`
} }
DeviceLoginRequest {
Identifier string `json:"identifier" validate:"required"`
IP string `header:"X-Original-Forwarded-For"`
UserAgent string `json:"user_agent" validate:"required"`
CfToken string `json:"cf_token,optional"`
}
) )
@server ( @server (
prefix: v1/auth prefix: v1/auth
group: auth group: auth
middleware: DeviceMiddleware
) )
service ppanel { service ppanel {
@doc "User login" @doc "User login"
@@ -164,10 +145,6 @@ service ppanel {
@doc "Reset password" @doc "Reset password"
@handler TelephoneResetPassword @handler TelephoneResetPassword
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse) post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
@doc "Device Login"
@handler DeviceLogin
post /login/device (DeviceLoginRequest) returns (LoginResponse)
} }
@server ( @server (
+2 -12
View File
@@ -87,17 +87,11 @@ type (
Total int64 `json:"total"` Total int64 `json:"total"`
List []SubscribeClient `json:"list"` List []SubscribeClient `json:"list"`
} }
HeartbeatResponse {
Status bool `json:"status"`
Message string `json:"message,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}
) )
@server ( @server (
prefix: v1/common prefix: v1/common
group: common group: common
middleware: DeviceMiddleware
) )
service ppanel { service ppanel {
@doc "Get global config" @doc "Get global config"
@@ -135,9 +129,5 @@ service ppanel {
@doc "Get Client" @doc "Get Client"
@handler GetClient @handler GetClient
get /client returns (GetSubscribeClientResponse) get /client returns (GetSubscribeClientResponse)
@doc "Heartbeat"
@handler Heartbeat
get /heartbeat returns (HeartbeatResponse)
} }
+1 -1
View File
@@ -13,7 +13,7 @@ import "../types.api"
@server ( @server (
prefix: v1/public/announcement prefix: v1/public/announcement
group: public/announcement group: public/announcement
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Query announcement" @doc "Query announcement"
+1 -1
View File
@@ -13,7 +13,7 @@ import "../types.api"
@server ( @server (
prefix: v1/public/document prefix: v1/public/document
group: public/document group: public/document
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Get document list" @doc "Get document list"
+1 -1
View File
@@ -13,7 +13,7 @@ import "../types.api"
@server ( @server (
prefix: v1/public/order prefix: v1/public/order
group: public/order group: public/order
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Pre create order" @doc "Pre create order"
+1 -1
View File
@@ -13,7 +13,7 @@ import "../types.api"
@server ( @server (
prefix: v1/public/payment prefix: v1/public/payment
group: public/payment group: public/payment
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Get available payment methods" @doc "Get available payment methods"
+2 -3
View File
@@ -68,9 +68,8 @@ type (
) )
@server ( @server (
prefix: v1/public/portal prefix: v1/public/portal
group: public/portal group: public/portal
middleware: DeviceMiddleware
) )
service ppanel { service ppanel {
@doc "Get available payment methods" @doc "Get available payment methods"
+1 -39
View File
@@ -14,54 +14,16 @@ type (
QuerySubscribeListRequest { QuerySubscribeListRequest {
Language string `form:"language"` Language string `form:"language"`
} }
QueryUserSubscribeNodeListResponse {
List []UserSubscribeInfo `json:"list"`
}
UserSubscribeInfo {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
OrderId int64 `json:"order_id"`
SubscribeId int64 `json:"subscribe_id"`
StartTime int64 `json:"start_time"`
ExpireTime int64 `json:"expire_time"`
FinishedAt int64 `json:"finished_at"`
ResetTime int64 `json:"reset_time"`
Traffic int64 `json:"traffic"`
Download int64 `json:"download"`
Upload int64 `json:"upload"`
Token string `json:"token"`
Status uint8 `json:"status"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
IsTryOut bool `json:"is_try_out"`
Nodes []*UserSubscribeNodeInfo `json:"nodes"`
}
UserSubscribeNodeInfo {
Id int64 `json:"id"`
Name string `json:"name"`
Uuid string `json:"uuid"`
Protocol string `json:"protocol"`
Port uint16 `json:"port"`
Address string `json:"address"`
Tags []string `json:"tags"`
Country string `json:"country"`
City string `json:"city"`
CreatedAt int64 `json:"created_at"`
}
) )
@server ( @server (
prefix: v1/public/subscribe prefix: v1/public/subscribe
group: public/subscribe group: public/subscribe
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Get subscribe list" @doc "Get subscribe list"
@handler QuerySubscribeList @handler QuerySubscribeList
get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse) get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse)
@doc "Get user subscribe node info"
@handler QueryUserSubscribeNodeList
get /node/list returns (QueryUserSubscribeNodeListResponse)
} }
+7 -5
View File
@@ -18,12 +18,14 @@ type (
CreateUserTicketRequest { CreateUserTicketRequest {
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Description string `json:"description"`
IssueType uint8 `json:"issue_type"`
} }
GetUserTicketListRequest { GetUserTicketListRequest {
Page int `form:"page"` Page int `form:"page"`
Size int `form:"size"` Size int `form:"size"`
Status *uint8 `form:"status,omitempty"` Status *uint8 `form:"status,omitempty"`
Search string `form:"search,omitempty"` IssueType *uint8 `form:"issue_type,omitempty"`
Search string `form:"search,omitempty"`
} }
GetUserTicketDetailRequest { GetUserTicketDetailRequest {
Id int64 `form:"id" validate:"required"` Id int64 `form:"id" validate:"required"`
@@ -43,7 +45,7 @@ type (
@server ( @server (
prefix: v1/public/ticket prefix: v1/public/ticket
group: public/ticket group: public/ticket
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Get ticket list" @doc "Get ticket list"
+1 -61
View File
@@ -97,48 +97,12 @@ type (
Email string `json:"email" validate:"required"` Email string `json:"email" validate:"required"`
Code string `json:"code" validate:"required"` Code string `json:"code" validate:"required"`
} }
GetDeviceListResponse {
List []UserDevice `json:"list"`
Total int64 `json:"total"`
}
UnbindDeviceRequest {
Id int64 `json:"id" validate:"required"`
}
UpdateUserSubscribeNoteRequest {
UserSubscribeId int64 `json:"user_subscribe_id" validate:"required"`
Note string `json:"note" validate:"max=500"`
}
UpdateUserRulesRequest {
Rules []string `json:"rules" validate:"required"`
}
CommissionWithdrawRequest {
Amount int64 `json:"amount"`
Content string `json:"content"`
}
WithdrawalLog {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
Amount int64 `json:"amount"`
Content string `json:"content"`
Status uint8 `json:"status"`
Reason string `json:"reason,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
QueryWithdrawalLogListRequest {
Page int `form:"page"`
Size int `form:"size"`
}
QueryWithdrawalLogListResponse {
List []WithdrawalLog `json:"list"`
Total int64 `json:"total"`
}
) )
@server ( @server (
prefix: v1/public/user prefix: v1/public/user
group: public/user group: public/user
middleware: AuthMiddleware,DeviceMiddleware middleware: AuthMiddleware
) )
service ppanel { service ppanel {
@doc "Query User Info" @doc "Query User Info"
@@ -228,29 +192,5 @@ service ppanel {
@doc "Update Bind Email" @doc "Update Bind Email"
@handler UpdateBindEmail @handler UpdateBindEmail
put /bind_email (UpdateBindEmailRequest) put /bind_email (UpdateBindEmailRequest)
@doc "Get Device List"
@handler GetDeviceList
get /devices returns (GetDeviceListResponse)
@doc "Unbind Device"
@handler UnbindDevice
put /unbind_device (UnbindDeviceRequest)
@doc "Update User Subscribe Note"
@handler UpdateUserSubscribeNote
put /subscribe_note (UpdateUserSubscribeNoteRequest)
@doc "Update User Rules"
@handler UpdateUserRules
put /rules (UpdateUserRulesRequest)
@doc "Commission Withdraw"
@handler CommissionWithdraw
post /commission_withdraw (CommissionWithdrawRequest) returns (WithdrawalLog)
@doc "Query Withdrawal Log"
@handler QueryWithdrawalLog
get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse)
} }
-9
View File
@@ -28,7 +28,6 @@ type (
EnableTradeNotify bool `json:"enable_trade_notify"` EnableTradeNotify bool `json:"enable_trade_notify"`
AuthMethods []UserAuthMethod `json:"auth_methods"` AuthMethods []UserAuthMethod `json:"auth_methods"`
UserDevices []UserDevice `json:"user_devices"` UserDevices []UserDevice `json:"user_devices"`
Rules []string `json:"rules"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
DeletedAt int64 `json:"deleted_at,omitempty"` DeletedAt int64 `json:"deleted_at,omitempty"`
@@ -116,7 +115,6 @@ type (
AuthConfig { AuthConfig {
Mobile MobileAuthenticateConfig `json:"mobile"` Mobile MobileAuthenticateConfig `json:"mobile"`
Email EmailAuthticateConfig `json:"email"` Email EmailAuthticateConfig `json:"email"`
Device DeviceAuthticateConfig `json:"device"`
Register PubilcRegisterConfig `json:"register"` Register PubilcRegisterConfig `json:"register"`
} }
PubilcRegisterConfig { PubilcRegisterConfig {
@@ -136,12 +134,6 @@ type (
EnableDomainSuffix bool `json:"enable_domain_suffix"` EnableDomainSuffix bool `json:"enable_domain_suffix"`
DomainSuffixList string `json:"domain_suffix_list"` DomainSuffixList string `json:"domain_suffix_list"`
} }
DeviceAuthticateConfig {
Enable bool `json:"enable"`
ShowAds bool `json:"show_ads"`
EnableSecurity bool `json:"enable_security"`
OnlyRealDevice bool `json:"only_real_device"`
}
RegisterConfig { RegisterConfig {
StopRegister bool `json:"stop_register"` StopRegister bool `json:"stop_register"`
EnableTrial bool `json:"enable_trial"` EnableTrial bool `json:"enable_trial"`
@@ -472,7 +464,6 @@ type (
Upload int64 `json:"upload"` Upload int64 `json:"upload"`
Token string `json:"token"` Token string `json:"token"`
Status uint8 `json:"status"` Status uint8 `json:"status"`
Short string `json:"short"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
} }
-42
View File
@@ -1,42 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/perfect-panel/server/initialize"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/conf"
"github.com/perfect-panel/server/pkg/logger"
"github.com/spf13/cobra"
)
func init() {
migrateCmd.Flags().StringVar(&migrateConfigPath, "config", "etc/ppanel.yaml", "ppanel.yaml directory to read from")
rootCmd.AddCommand(migrateCmd)
}
var (
migrateConfigPath string
)
var migrateCmd = &cobra.Command{
Use: "migrate",
Short: "Run database migrations",
Run: func(cmd *cobra.Command, args []string) {
runMigrate()
},
}
func runMigrate() {
var c config.Config
conf.MustLoad(migrateConfigPath, &c)
if err := logger.SetUp(c.Logger); err != nil {
fmt.Println("Logger setup failed:", err.Error())
os.Exit(1)
}
ctx := svc.NewServiceContext(c)
initialize.Migrate(ctx)
logger.Info("[Migrate] database migration completed")
}
+15 -3
View File
@@ -2,11 +2,11 @@ Host: 0.0.0.0
Port: 8080 Port: 8080
Debug: false Debug: false
JwtAuth: JwtAuth:
AccessSecret: 1234567890 AccessSecret: e4bbdb8ca57179374c0e8a7a85038da21341aee4bdff98c0e612751d7c362cac
AccessExpire: 604800 AccessExpire: 604800
Logger: Logger:
ServiceName: PPanel ServiceName: PPanel
Mode: console Mode: file
Encoding: plain Encoding: plain
TimeFormat: '2025-01-01 00:00:00.000' TimeFormat: '2025-01-01 00:00:00.000'
Path: logs Path: logs
@@ -21,7 +21,7 @@ Logger:
Rotation: daily Rotation: daily
FileTimeFormat: 2025-01-01T00:00:00.000Z00:00 FileTimeFormat: 2025-01-01T00:00:00.000Z00:00
MySQL: MySQL:
Addr: 172.245.180.199:3306 Addr: ppanel-db:3306
Dbname: ppanel Dbname: ppanel
Username: ppanel Username: ppanel
Password: ppanelpassword Password: ppanelpassword
@@ -36,3 +36,15 @@ Redis:
Administrator: Administrator:
Password: password Password: password
Email: admin@ppanel.dev Email: admin@ppanel.dev
Email:
Enable: true
Platform: smtp
PlatformConfig: '{"host":"smtp.gmail.com","port":465,"user":"devneeds52@gmail.com","pass":"bqxn hurw ckxt ookj","from":"devneeds52@gmail.com","ssl":true}'
EnableVerify: true
EnableNotify: true
EnableDomainSuffix: false
DomainSuffixList: ""
VerifyEmailTemplate: ""
ExpirationEmailTemplate: ""
MaintenanceEmailTemplate: ""
TrafficExceedEmailTemplate: ""
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -5
View File
@@ -44,7 +44,7 @@ require (
go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/sdk v1.29.0
go.opentelemetry.io/otel/trace v1.29.0 go.opentelemetry.io/otel/trace v1.29.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.35.0 golang.org/x/crypto v0.32.0
golang.org/x/oauth2 v0.25.0 golang.org/x/oauth2 v0.25.0
golang.org/x/time v0.6.0 golang.org/x/time v0.6.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
@@ -60,7 +60,6 @@ require (
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/goccy/go-json v0.10.4 github.com/goccy/go-json v0.10.4
github.com/golang-migrate/migrate/v4 v4.18.2 github.com/golang-migrate/migrate/v4 v4.18.2
github.com/oschwald/geoip2-golang v1.13.0
github.com/spaolacci/murmur3 v1.1.0 github.com/spaolacci/murmur3 v1.1.0
google.golang.org/grpc v1.64.1 google.golang.org/grpc v1.64.1
google.golang.org/protobuf v1.36.3 google.golang.org/protobuf v1.36.3
@@ -118,7 +117,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/openzipkin/zipkin-go v0.4.2 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
@@ -140,8 +138,8 @@ require (
golang.org/x/arch v0.13.0 // indirect golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+6 -10
View File
@@ -285,10 +285,6 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -406,8 +402,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -467,8 +463,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -482,8 +478,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+1 -22
View File
@@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/perfect-panel/server/internal/report"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
@@ -36,30 +35,10 @@ func Config(path string) (chan bool, *http.Server) {
configPath = path configPath = path
// Create a new Gin instance // Create a new Gin instance
r := gin.Default() r := gin.Default()
// get server port
port := 8080
host := "127.0.0.1"
// check gateway mode
if report.IsGatewayMode() {
// get free port
freePort, err := report.ModulePort()
if err != nil {
logger.Errorf("get module port error: %s", err.Error())
panic(err)
}
port = freePort
// register module
err = report.RegisterModule(port)
if err != nil {
logger.Errorf("register module error: %s", err.Error())
panic(err)
}
logger.Infof("module registered on port %d", port)
}
// Create a new HTTP server // Create a new HTTP server
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port), Addr: ":8080",
Handler: r, Handler: r,
} }
// Load templates // Load templates
-26
View File
@@ -1,26 +0,0 @@
package initialize
import (
"context"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/model/auth"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/tool"
)
func Device(ctx *svc.ServiceContext) {
logger.Debug("device config initialization")
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "device")
if err != nil {
panic(err)
}
var cfg config.DeviceConfig
var deviceConfig auth.DeviceConfig
deviceConfig.Unmarshal(method.Config)
tool.DeepCopy(&cfg, deviceConfig)
cfg.Enable = *method.Enabled
ctx.Config.Device = cfg
}
-1
View File
@@ -9,7 +9,6 @@ func StartInitSystemConfig(svc *svc.ServiceContext) {
Site(svc) Site(svc)
Node(svc) Node(svc)
Email(svc) Email(svc)
Device(svc)
Invite(svc) Invite(svc)
Verify(svc) Verify(svc)
Subscribe(svc) Subscribe(svc)
@@ -379,6 +379,7 @@ CREATE TABLE IF NOT EXISTS `user`
`referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID', `referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID',
`commission` bigint DEFAULT '0' COMMENT 'Commission', `commission` bigint DEFAULT '0' COMMENT 'Commission',
`gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount', `gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'User Remark',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled', `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin', `is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin',
`valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified', `valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified',
@@ -1,20 +0,0 @@
-- 只有当 ads 表中不存在 description 字段时才添加
SET
@col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'ads'
AND COLUMN_NAME = 'description'
);
SET
@query := IF(
@col_exists = 0,
'ALTER TABLE `ads` ADD COLUMN `description` VARCHAR(255) DEFAULT '''' COMMENT ''Description'';',
'SELECT "Column `description` already exists"'
);
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -1,3 +0,0 @@
ALTER TABLE `user`
DROP COLUMN `algo`,
DROP COLUMN `salt`;
@@ -1,35 +0,0 @@
-- 添加 algo 列(如果不存在)
SET @dbname = DATABASE();
SET @tablename = 'user';
SET @colname = 'algo';
SET @sql = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE `user` ADD COLUMN `algo` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Encryption Algorithm'' AFTER `password`;',
'SELECT "Column `algo` already exists";'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = @tablename
AND COLUMN_NAME = @colname
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 添加 salt 列(如果不存在)
SET @colname = 'salt';
SET @sql = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE `user` ADD COLUMN `salt` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Password Salt'' AFTER `algo`;',
'SELECT "Column `salt` already exists";'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = @tablename
AND COLUMN_NAME = @colname
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -1,7 +0,0 @@
INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
SELECT 'site', 'CustomData', '{
"kr_website_id": ""
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
WHERE NOT EXISTS (
SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData'
);
@@ -1,7 +0,0 @@
INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
SELECT 'site', 'CustomData', '{
"kr_website_id": ""
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
WHERE NOT EXISTS (
SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData'
);
@@ -1 +0,0 @@
ALTER TABLE traffic_log DROP INDEX idx_timestamp;
@@ -1 +0,0 @@
ALTER TABLE traffic_log ADD INDEX idx_timestamp (timestamp);
@@ -1,2 +0,0 @@
ALTER TABLE `user_subscribe`
DROP COLUMN `note`;
@@ -1,4 +0,0 @@
ALTER TABLE `user_subscribe`
ADD COLUMN `note` VARCHAR(500) NOT NULL DEFAULT ''
COMMENT 'User note for subscription'
AFTER `status`;
@@ -1,2 +0,0 @@
ALTER TABLE `user`
DROP COLUMN IF EXISTS `rules`;
@@ -1,4 +0,0 @@
ALTER TABLE `user`
ADD COLUMN `rules` TEXT NULL
COMMENT 'User rules for subscription'
AFTER `created_at`;
@@ -1,5 +0,0 @@
DROP TABLE IF EXISTS `withdrawals`;
DELETE FROM `system`
WHERE `category` = 'invite'
AND `key` = 'WithdrawalMethod';
@@ -1,16 +0,0 @@
CREATE TABLE IF NOT EXISTS `withdrawals` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`user_id` BIGINT NOT NULL COMMENT 'User ID',
`amount` BIGINT NOT NULL COMMENT 'Withdrawal Amount',
`content` TEXT COMMENT 'Withdrawal Content',
`status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Withdrawal Status',
`reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'Rejection Reason',
`created_at` DATETIME NOT NULL COMMENT 'Creation Time',
`updated_at` DATETIME NOT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
VALUES
('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637');
-4
View File
@@ -2,7 +2,6 @@ package initialize
import ( import (
"errors" "errors"
"time"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
"gorm.io/gorm" "gorm.io/gorm"
@@ -17,7 +16,6 @@ func Migrate(ctx *svc.ServiceContext) {
mc := orm.Mysql{ mc := orm.Mysql{
Config: ctx.Config.MySQL, Config: ctx.Config.MySQL,
} }
now := time.Now()
if err := migrate.Migrate(mc.Dsn()).Up(); err != nil { if err := migrate.Migrate(mc.Dsn()).Up(); err != nil {
if errors.Is(err, migrate.NoChange) { if errors.Is(err, migrate.NoChange) {
logger.Info("[Migrate] database not change") logger.Info("[Migrate] database not change")
@@ -25,8 +23,6 @@ func Migrate(ctx *svc.ServiceContext) {
} }
logger.Errorf("[Migrate] Up error: %v", err.Error()) logger.Errorf("[Migrate] Up error: %v", err.Error())
panic(err) panic(err)
} else {
logger.Info("[Migrate] Database change, took " + time.Since(now).String())
} }
// if not found admin user // if not found admin user
err := ctx.DB.Transaction(func(tx *gorm.DB) error { err := ctx.DB.Transaction(func(tx *gorm.DB) error {
+3 -15
View File
@@ -2,7 +2,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/orm" "github.com/perfect-panel/server/pkg/orm"
) )
@@ -21,7 +20,6 @@ type Config struct {
Node NodeConfig `yaml:"Node"` Node NodeConfig `yaml:"Node"`
Mobile MobileConfig `yaml:"Mobile"` Mobile MobileConfig `yaml:"Mobile"`
Email EmailConfig `yaml:"Email"` Email EmailConfig `yaml:"Email"`
Device DeviceConfig `yaml:"device"`
Verify Verify `yaml:"Verify"` Verify Verify `yaml:"Verify"`
VerifyCode VerifyCode `yaml:"VerifyCode"` VerifyCode VerifyCode `yaml:"VerifyCode"`
Register RegisterConfig `yaml:"Register"` Register RegisterConfig `yaml:"Register"`
@@ -97,14 +95,6 @@ type MobileConfig struct {
Whitelist []string `yaml:"whitelist"` Whitelist []string `yaml:"whitelist"`
} }
type DeviceConfig struct {
Enable bool `yaml:"enable" default:"true"`
ShowAds bool `yaml:"show_ads"`
EnableSecurity bool `yaml:"enable_security"`
OnlyRealDevice bool `yaml:"only_real_device"`
SecuritySecret string `yaml:"security_secret"`
}
type SiteConfig struct { type SiteConfig struct {
Host string `yaml:"Host" default:""` Host string `yaml:"Host" default:""`
SiteName string `yaml:"SiteName" default:""` SiteName string `yaml:"SiteName" default:""`
@@ -200,12 +190,10 @@ type File struct {
} }
type InviteConfig struct { type InviteConfig struct {
ForcedInvite bool `yaml:"ForcedInvite" default:"false"` ForcedInvite bool `yaml:"ForcedInvite" default:"false"`
FirstPurchasePercentage int64 `yaml:"FirstPurchasePercentage" default:"20"` FirstPurchasePercentage int64 `yaml:"FirstPurchasePercentage" default:"20"`
FirstYearlyPurchasePercentage int64 `yaml:"FirstYearlyPurchasePercentage" default:"25"` FirstYearlyPurchasePercentage int64 `yaml:"FirstYearlyPurchasePercentage" default:"25"`
NonFirstPurchasePercentage int64 `yaml:"NonFirstPurchasePercentage" default:"10"` NonFirstPurchasePercentage int64 `yaml:"NonFirstPurchasePercentage" default:"10"`
ReferralPercentage uint8 `yaml:"ReferralPercentage" default:"10"`
OnlyFirstPurchase bool `yaml:"OnlyFirstPurchase" default:"false"`
} }
type Telegram struct { type Telegram struct {
@@ -1,18 +0,0 @@
package subscribe
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/subscribe"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Reset all subscribe tokens
func ResetAllSubscribeTokenHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := subscribe.NewResetAllSubscribeTokenLogic(c.Request.Context(), svcCtx)
resp, err := l.ResetAllSubscribeToken()
result.HttpResult(c, resp, err)
}
}
@@ -1,18 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/system"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// GetModuleConfigHandler Get Module Config
func GetModuleConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := system.NewGetModuleConfigLogic(c.Request.Context(), svcCtx)
resp, err := l.GetModuleConfig()
result.HttpResult(c, resp, err)
}
}
@@ -1,26 +0,0 @@
package tool
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/tool"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// QueryIPLocationHandler Query IP Location
func QueryIPLocationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryIPLocationRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := tool.NewQueryIPLocationLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryIPLocation(&req)
result.HttpResult(c, resp, err)
}
}
@@ -1,26 +0,0 @@
package auth
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/auth"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Device Login
func DeviceLoginHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.DeviceLoginRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := auth.NewDeviceLoginLogic(c.Request.Context(), svcCtx)
resp, err := l.DeviceLogin(&req)
result.HttpResult(c, resp, err)
}
}
@@ -10,6 +10,7 @@ import (
// Get Client // Get Client
func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) { return func(c *gin.Context) {
l := common.NewGetClientLogic(c.Request.Context(), svcCtx) l := common.NewGetClientLogic(c.Request.Context(), svcCtx)
resp, err := l.GetClient() resp, err := l.GetClient()
result.HttpResult(c, resp, err) result.HttpResult(c, resp, err)
@@ -1,18 +0,0 @@
package common
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/common"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Heartbeat
func HeartbeatHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := common.NewHeartbeatLogic(c.Request.Context(), svcCtx)
resp, err := l.Heartbeat()
result.HttpResult(c, resp, err)
}
}
@@ -1,18 +0,0 @@
package subscribe
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/subscribe"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Get user subscribe node info
func QueryUserSubscribeNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := subscribe.NewQueryUserSubscribeNodeListLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryUserSubscribeNodeList()
result.HttpResult(c, resp, err)
}
}
@@ -1,26 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Commission Withdraw
func CommissionWithdrawHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.CommissionWithdrawRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewCommissionWithdrawLogic(c.Request.Context(), svcCtx)
resp, err := l.CommissionWithdraw(&req)
result.HttpResult(c, resp, err)
}
}
@@ -1,18 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Get Device List
func GetDeviceListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := user.NewGetDeviceListLogic(c.Request.Context(), svcCtx)
resp, err := l.GetDeviceList()
result.HttpResult(c, resp, err)
}
}
@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user" "github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result" "github.com/perfect-panel/server/pkg/result"
) )
@@ -11,8 +12,15 @@ import (
func QueryUserBalanceLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { func QueryUserBalanceLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) { return func(c *gin.Context) {
var req types.QueryUserBalanceLogListRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewQueryUserBalanceLogLogic(c.Request.Context(), svcCtx) l := user.NewQueryUserBalanceLogLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryUserBalanceLog() resp, err := l.QueryUserBalanceLog(&req)
result.HttpResult(c, resp, err) result.HttpResult(c, resp, err)
} }
} }
@@ -1,26 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Query Withdrawal Log
func QueryWithdrawalLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryWithdrawalLogListRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewQueryWithdrawalLogLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryWithdrawalLog(&req)
result.HttpResult(c, resp, err)
}
}
@@ -1,26 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Unbind Device
func UnbindDeviceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.UnbindDeviceRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewUnbindDeviceLogic(c.Request.Context(), svcCtx)
err := l.UnbindDevice(&req)
result.HttpResult(c, nil, err)
}
}
@@ -1,26 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Update User Rules
func UpdateUserRulesHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.UpdateUserRulesRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewUpdateUserRulesLogic(c.Request.Context(), svcCtx)
err := l.UpdateUserRules(&req)
result.HttpResult(c, nil, err)
}
}
@@ -1,26 +0,0 @@
package user
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/public/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Update User Subscribe Note
func UpdateUserSubscribeNoteHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.UpdateUserSubscribeNoteRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := user.NewUpdateUserSubscribeNoteLogic(c.Request.Context(), svcCtx)
err := l.UpdateUserSubscribeNote(&req)
result.HttpResult(c, nil, err)
}
}
+7 -46
View File
@@ -385,9 +385,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Get subscribe list // Get subscribe list
adminSubscribeGroupRouter.GET("/list", adminSubscribe.GetSubscribeListHandler(serverCtx)) adminSubscribeGroupRouter.GET("/list", adminSubscribe.GetSubscribeListHandler(serverCtx))
// Reset all subscribe tokens
adminSubscribeGroupRouter.POST("/reset_all_token", adminSubscribe.ResetAllSubscribeTokenHandler(serverCtx))
// Subscribe sort // Subscribe sort
adminSubscribeGroupRouter.POST("/sort", adminSubscribe.SubscribeSortHandler(serverCtx)) adminSubscribeGroupRouter.POST("/sort", adminSubscribe.SubscribeSortHandler(serverCtx))
} }
@@ -411,9 +408,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Update invite config // Update invite config
adminSystemGroupRouter.PUT("/invite_config", adminSystem.UpdateInviteConfigHandler(serverCtx)) adminSystemGroupRouter.PUT("/invite_config", adminSystem.UpdateInviteConfigHandler(serverCtx))
// Get Module Config
adminSystemGroupRouter.GET("/module", adminSystem.GetModuleConfigHandler(serverCtx))
// Get node config // Get node config
adminSystemGroupRouter.GET("/node_config", adminSystem.GetNodeConfigHandler(serverCtx)) adminSystemGroupRouter.GET("/node_config", adminSystem.GetNodeConfigHandler(serverCtx))
@@ -493,9 +487,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
adminToolGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) adminToolGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Query IP Location
adminToolGroupRouter.GET("/ip/location", adminTool.QueryIPLocationHandler(serverCtx))
// Get System Log // Get System Log
adminToolGroupRouter.GET("/log", adminTool.GetSystemLogHandler(serverCtx)) adminToolGroupRouter.GET("/log", adminTool.GetSystemLogHandler(serverCtx))
@@ -587,7 +578,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
authGroupRouter := router.Group("/v1/auth") authGroupRouter := router.Group("/v1/auth")
authGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
{ {
// Check user is exist // Check user is exist
@@ -599,9 +589,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// User login // User login
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx)) authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
// Device Login
authGroupRouter.POST("/login/device", auth.DeviceLoginHandler(serverCtx))
// User Telephone login // User Telephone login
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx)) authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
@@ -632,7 +619,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
commonGroupRouter := router.Group("/v1/common") commonGroupRouter := router.Group("/v1/common")
commonGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
{ {
// Get Ads // Get Ads
@@ -644,9 +630,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Get Client // Get Client
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx)) commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
// Heartbeat
commonGroupRouter.GET("/heartbeat", common.HeartbeatHandler(serverCtx))
// Get verification code // Get verification code
commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx)) commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx))
@@ -667,7 +650,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicAnnouncementGroupRouter := router.Group("/v1/public/announcement") publicAnnouncementGroupRouter := router.Group("/v1/public/announcement")
publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Query announcement // Query announcement
@@ -675,7 +658,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicDocumentGroupRouter := router.Group("/v1/public/document") publicDocumentGroupRouter := router.Group("/v1/public/document")
publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Get document detail // Get document detail
@@ -686,7 +669,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicOrderGroupRouter := router.Group("/v1/public/order") publicOrderGroupRouter := router.Group("/v1/public/order")
publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Close order // Close order
@@ -715,7 +698,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicPaymentGroupRouter := router.Group("/v1/public/payment") publicPaymentGroupRouter := router.Group("/v1/public/payment")
publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Get available payment methods // Get available payment methods
@@ -723,7 +706,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicPortalGroupRouter := router.Group("/v1/public/portal") publicPortalGroupRouter := router.Group("/v1/public/portal")
publicPortalGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
{ {
// Purchase Checkout // Purchase Checkout
@@ -746,18 +728,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe") publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Get subscribe list // Get subscribe list
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx)) publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
// Get user subscribe node info
publicSubscribeGroupRouter.GET("/node/list", publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx))
} }
publicTicketGroupRouter := router.Group("/v1/public/ticket") publicTicketGroupRouter := router.Group("/v1/public/ticket")
publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Update ticket status // Update ticket status
@@ -777,7 +756,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
} }
publicUserGroupRouter := router.Group("/v1/public/user") publicUserGroupRouter := router.Group("/v1/public/user")
publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{ {
// Query User Affiliate Count // Query User Affiliate Count
@@ -807,12 +786,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Query User Commission Log // Query User Commission Log
publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx)) publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx))
// Commission Withdraw
publicUserGroupRouter.POST("/commission_withdraw", publicUser.CommissionWithdrawHandler(serverCtx))
// Get Device List
publicUserGroupRouter.GET("/devices", publicUser.GetDeviceListHandler(serverCtx))
// Query User Info // Query User Info
publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx)) publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx))
@@ -828,24 +801,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Update User Password // Update User Password
publicUserGroupRouter.PUT("/password", publicUser.UpdateUserPasswordHandler(serverCtx)) publicUserGroupRouter.PUT("/password", publicUser.UpdateUserPasswordHandler(serverCtx))
// Update User Rules
publicUserGroupRouter.PUT("/rules", publicUser.UpdateUserRulesHandler(serverCtx))
// Query User Subscribe // Query User Subscribe
publicUserGroupRouter.GET("/subscribe", publicUser.QueryUserSubscribeHandler(serverCtx)) publicUserGroupRouter.GET("/subscribe", publicUser.QueryUserSubscribeHandler(serverCtx))
// Get Subscribe Log // Get Subscribe Log
publicUserGroupRouter.GET("/subscribe_log", publicUser.GetSubscribeLogHandler(serverCtx)) publicUserGroupRouter.GET("/subscribe_log", publicUser.GetSubscribeLogHandler(serverCtx))
// Update User Subscribe Note
publicUserGroupRouter.PUT("/subscribe_note", publicUser.UpdateUserSubscribeNoteHandler(serverCtx))
// Reset User Subscribe Token // Reset User Subscribe Token
publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx)) publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx))
// Unbind Device
publicUserGroupRouter.PUT("/unbind_device", publicUser.UnbindDeviceHandler(serverCtx))
// Unbind OAuth // Unbind OAuth
publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx)) publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx))
@@ -860,9 +824,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Verify Email // Verify Email
publicUserGroupRouter.POST("/verify_email", publicUser.VerifyEmailHandler(serverCtx)) publicUserGroupRouter.POST("/verify_email", publicUser.VerifyEmailHandler(serverCtx))
// Query Withdrawal Log
publicUserGroupRouter.GET("/withdrawal_log", publicUser.QueryWithdrawalLogHandler(serverCtx))
} }
serverGroupRouter := router.Group("/v1/server") serverGroupRouter := router.Group("/v1/server")
@@ -36,12 +36,6 @@ func QueryServerProtocolConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Co
fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req) fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req)
if svcCtx.Config.Node.NodeSecret != req.SecretKey {
c.String(http.StatusUnauthorized, "Unauthorized")
c.Abort()
return
}
l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx) l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryServerProtocolConfig(&req) resp, err := l.QueryServerProtocolConfig(&req)
result.HttpResult(c, resp, err) result.HttpResult(c, resp, err)
-16
View File
@@ -23,22 +23,6 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
ua := c.GetHeader("User-Agent") ua := c.GetHeader("User-Agent")
req.UA = c.Request.Header.Get("User-Agent") req.UA = c.Request.Header.Get("User-Agent")
req.Flag = c.Query("flag") req.Flag = c.Query("flag")
if svcCtx.Config.Subscribe.PanDomain {
domain := c.Request.Host
domainArr := strings.Split(domain, ".")
short, err := tool.FixedUniqueString(req.Token, 8, "")
if err != nil {
logger.Errorf("[SubscribeHandler] Generate short token failed: %v", err)
c.String(http.StatusInternalServerError, "Internal Server")
c.Abort()
return
}
if short != domainArr[0] {
c.String(http.StatusForbidden, "Access denied")
c.Abort()
return
}
}
if svcCtx.Config.Subscribe.UserAgentLimit { if svcCtx.Config.Subscribe.UserAgentLimit {
if ua == "" { if ua == "" {
@@ -29,12 +29,10 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
} }
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) { func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
enable := true
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{ _, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: 1, Page: 1,
Size: 1000, Size: 1000,
Preload: true, Preload: true,
Enabled: &enable,
}) })
if err != nil { if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error()) l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
@@ -92,9 +92,6 @@ func (l *UpdateAuthMethodConfigLogic) UpdateGlobal(method string) {
if method == "mobile" { if method == "mobile" {
initialize.Mobile(l.svcCtx) initialize.Mobile(l.svcCtx)
} }
if method == "device" {
initialize.Device(l.svcCtx)
}
} }
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) { func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/perfect-panel/server/internal/report"
paymentPlatform "github.com/perfect-panel/server/pkg/payment" paymentPlatform "github.com/perfect-panel/server/pkg/payment"
"github.com/perfect-panel/server/internal/model/payment" "github.com/perfect-panel/server/internal/model/payment"
@@ -44,31 +43,15 @@ func (l *GetPaymentMethodListLogic) GetPaymentMethodList(req *types.GetPaymentMe
Total: total, Total: total,
List: make([]types.PaymentMethodDetail, len(list)), List: make([]types.PaymentMethodDetail, len(list)),
} }
// gateway mod
isGatewayMod := report.IsGatewayMode()
for i, v := range list { for i, v := range list {
config := make(map[string]interface{}) config := make(map[string]interface{})
_ = json.Unmarshal([]byte(v.Config), &config) _ = json.Unmarshal([]byte(v.Config), &config)
notifyUrl := "" notifyUrl := ""
if paymentPlatform.ParsePlatform(v.Platform) != paymentPlatform.Balance { if paymentPlatform.ParsePlatform(v.Platform) != paymentPlatform.Balance {
notifyUrl = v.Domain
if v.Domain != "" { if v.Domain != "" {
// if is gateway mod, use gateway domain notifyUrl = v.Domain + "/v1/notify/" + v.Platform + "/" + v.Token
if isGatewayMod {
notifyUrl += "/api/"
}
notifyUrl += "/v1/notify/" + v.Platform + "/" + v.Token
} else { } else {
notifyUrl += "https://" + l.svcCtx.Config.Host notifyUrl = "https://" + l.svcCtx.Config.Host + "/v1/notify/" + v.Platform + "/" + v.Token
if isGatewayMod {
notifyUrl += "/api/v1/notify/" + v.Platform + "/" + v.Token
} else {
notifyUrl += "/v1/notify/" + v.Platform + "/" + v.Token
}
} }
} }
resp.List[i] = types.PaymentMethodDetail{ resp.List[i] = types.PaymentMethodDetail{
@@ -1,61 +0,0 @@
package subscribe
import (
"context"
"strconv"
"time"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/pkg/uuidx"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type ResetAllSubscribeTokenLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Reset all subscribe tokens
func NewResetAllSubscribeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetAllSubscribeTokenLogic {
return &ResetAllSubscribeTokenLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ResetAllSubscribeTokenLogic) ResetAllSubscribeToken() (resp *types.ResetAllSubscribeTokenResponse, err error) {
var list []*user.Subscribe
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
// select all active and Finished subscriptions
if err = tx.Model(&user.Subscribe{}).Where("`status` IN ?", []int64{1, 2}).Find(&list).Error; err != nil {
logger.Errorf("[ResetAllSubscribeToken] Failed to fetch subscribe list: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to fetch subscribe list: %v", err.Error())
}
for _, sub := range list {
sub.Token = uuidx.SubscribeToken(strconv.FormatInt(time.Now().UnixMilli(), 10) + strconv.FormatInt(sub.Id, 10))
sub.UUID = uuidx.NewUUID().String()
if err = tx.Model(&user.Subscribe{}).Where("id = ?", sub.Id).Save(sub).Error; err != nil {
tx.Rollback()
logger.Errorf("[ResetAllSubscribeToken] Failed to update subscribe token for ID %d: %v", sub.Id, err.Error())
return &types.ResetAllSubscribeTokenResponse{
Success: false,
}, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update subscribe token for ID %d: %v", sub.Id, err.Error())
}
}
if err = tx.Commit().Error; err != nil {
logger.Errorf("[ResetAllSubscribeToken] Failed to commit transaction: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to commit transaction: %v", err.Error())
}
return &types.ResetAllSubscribeTokenResponse{
Success: true,
}, nil
}
@@ -1,41 +0,0 @@
package system
import (
"context"
"os"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetModuleConfigLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get Module Config
func NewGetModuleConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetModuleConfigLogic {
return &GetModuleConfigLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetModuleConfigLogic) GetModuleConfig() (resp *types.ModuleConfig, err error) {
value, exists := os.LookupEnv("SECRET_KEY")
if !exists {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), " SECRET_KEY not set in environment variables")
}
return &types.ModuleConfig{
Secret: value,
ServiceName: constant.ServiceName,
ServiceVersion: constant.Version,
}, nil
}
@@ -27,7 +27,7 @@ func NewGetTicketListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
} }
func (l *GetTicketListLogic) GetTicketList(req *types.GetTicketListRequest) (resp *types.GetTicketListResponse, err error) { func (l *GetTicketListLogic) GetTicketList(req *types.GetTicketListRequest) (resp *types.GetTicketListResponse, err error) {
total, list, err := l.svcCtx.TicketModel.QueryTicketList(l.ctx, int(req.Page), int(req.Size), req.UserId, req.Status, req.Search) total, list, err := l.svcCtx.TicketModel.QueryTicketList(l.ctx, int(req.Page), int(req.Size), req.UserId, req.Status, req.IssueType, req.Search)
if err != nil { if err != nil {
l.Errorw("[GetTicketList] Query Database Error: ", logger.Field("error", err.Error())) l.Errorw("[GetTicketList] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryTicketList error: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryTicketList error: %v", err)
@@ -1,57 +0,0 @@
package tool
import (
"context"
"net"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type QueryIPLocationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewQueryIPLocationLogic Query IP Location
func NewQueryIPLocationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryIPLocationLogic {
return &QueryIPLocationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryIPLocationLogic) QueryIPLocation(req *types.QueryIPLocationRequest) (resp *types.QueryIPLocationResponse, err error) {
if l.svcCtx.GeoIP == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), " GeoIP database not configured")
}
ip := net.ParseIP(req.IP)
record, err := l.svcCtx.GeoIP.DB.City(ip)
if err != nil {
l.Errorf("Failed to query IP location: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to query IP location")
}
var country, region, city string
if record.Country.Names != nil {
country = record.Country.Names["en"]
}
if len(record.Subdivisions) > 0 && record.Subdivisions[0].Names != nil {
region = record.Subdivisions[0].Names["en"]
}
if record.City.Names != nil {
city = record.City.Names["en"]
}
return &types.QueryIPLocationResponse{
Country: country,
Region: region,
City: city,
}, nil
}
@@ -40,7 +40,6 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) error {
pwd := tool.EncodePassWord(req.Password) pwd := tool.EncodePassWord(req.Password)
newUser := &user.User{ newUser := &user.User{
Password: pwd, Password: pwd,
Algo: "default",
ReferralPercentage: req.ReferralPercentage, ReferralPercentage: req.ReferralPercentage,
OnlyFirstPurchase: &req.OnlyFirstPurchase, OnlyFirstPurchase: &req.OnlyFirstPurchase,
ReferCode: req.ReferCode, ReferCode: req.ReferCode,
@@ -129,7 +129,6 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow modification of the admin user password"), "UpdateUserBasicInfo failed: cannot update admin user password in demo mode") return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow modification of the admin user password"), "UpdateUserBasicInfo failed: cannot update admin user password in demo mode")
} }
userInfo.Password = tool.EncodePassWord(req.Password) userInfo.Password = tool.EncodePassWord(req.Password)
userInfo.Algo = "default"
} }
err = l.svcCtx.UserModel.Update(l.ctx, userInfo) err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
-234
View File
@@ -1,234 +0,0 @@
package auth
import (
"context"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type BindDeviceLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewBindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindDeviceLogic {
return &BindDeviceLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// BindDeviceToUser binds a device to a user
// If the device is already bound to another user, it will disable that user and bind the device to the current user
func (l *BindDeviceLogic) BindDeviceToUser(identifier, ip, userAgent string, currentUserId int64) error {
if identifier == "" {
// No device identifier provided, skip binding
return nil
}
l.Infow("binding device to user",
logger.Field("identifier", identifier),
logger.Field("user_id", currentUserId),
logger.Field("ip", ip),
)
// Check if device exists
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Device not found, create new device record
return l.createDeviceForUser(identifier, ip, userAgent, currentUserId)
}
l.Errorw("failed to query device",
logger.Field("identifier", identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
}
// Device exists, check if it's bound to current user
if deviceInfo.UserId == currentUserId {
// Already bound to current user, just update IP and UserAgent
l.Infow("device already bound to current user, updating info",
logger.Field("identifier", identifier),
logger.Field("user_id", currentUserId),
)
deviceInfo.Ip = ip
deviceInfo.UserAgent = userAgent
if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {
l.Errorw("failed to update device",
logger.Field("identifier", identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err.Error())
}
return nil
}
// Device is bound to another user, need to disable old user and rebind
l.Infow("device bound to another user, rebinding",
logger.Field("identifier", identifier),
logger.Field("old_user_id", deviceInfo.UserId),
logger.Field("new_user_id", currentUserId),
)
return l.rebindDeviceToNewUser(deviceInfo, ip, userAgent, currentUserId)
}
func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, userId int64) error {
l.Infow("creating new device for user",
logger.Field("identifier", identifier),
logger.Field("user_id", userId),
)
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
// Create device auth method
authMethod := &user.AuthMethods{
UserId: userId,
AuthType: "device",
AuthIdentifier: identifier,
Verified: true,
}
if err := db.Create(authMethod).Error; err != nil {
l.Errorw("failed to create device auth method",
logger.Field("user_id", userId),
logger.Field("identifier", identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
}
// Create device record
deviceInfo := &user.Device{
Ip: ip,
UserId: userId,
UserAgent: userAgent,
Identifier: identifier,
Enabled: true,
Online: false,
}
if err := db.Create(deviceInfo).Error; err != nil {
l.Errorw("failed to create device",
logger.Field("user_id", userId),
logger.Field("identifier", identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device failed: %v", err)
}
return nil
})
if err != nil {
l.Errorw("device creation failed",
logger.Field("identifier", identifier),
logger.Field("user_id", userId),
logger.Field("error", err.Error()),
)
return err
}
l.Infow("device created successfully",
logger.Field("identifier", identifier),
logger.Field("user_id", userId),
)
return nil
}
func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error {
oldUserId := deviceInfo.UserId
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
// Check if old user has other auth methods besides device
var authMethods []user.AuthMethods
if err := db.Where("user_id = ?", oldUserId).Find(&authMethods).Error; err != nil {
l.Errorw("failed to query auth methods for old user",
logger.Field("old_user_id", oldUserId),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query auth methods failed: %v", err)
}
// Count non-device auth methods
nonDeviceAuthCount := 0
for _, auth := range authMethods {
if auth.AuthType != "device" {
nonDeviceAuthCount++
}
}
// Only disable old user if they have no other auth methods
if nonDeviceAuthCount == 0 {
falseVal := false
if err := db.Model(&user.User{}).Where("id = ?", oldUserId).Update("enable", &falseVal).Error; err != nil {
l.Errorw("failed to disable old user",
logger.Field("old_user_id", oldUserId),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable old user failed: %v", err)
}
l.Infow("disabled old user (no other auth methods)",
logger.Field("old_user_id", oldUserId),
)
} else {
l.Infow("old user has other auth methods, not disabling",
logger.Field("old_user_id", oldUserId),
logger.Field("non_device_auth_count", nonDeviceAuthCount),
)
}
// Update device auth method to new user
if err := db.Model(&user.AuthMethods{}).
Where("auth_type = ? AND auth_identifier = ?", "device", deviceInfo.Identifier).
Update("user_id", newUserId).Error; err != nil {
l.Errorw("failed to update device auth method",
logger.Field("identifier", deviceInfo.Identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device auth method failed: %v", err)
}
// Update device record
deviceInfo.UserId = newUserId
deviceInfo.Ip = ip
deviceInfo.UserAgent = userAgent
deviceInfo.Enabled = true
if err := db.Save(deviceInfo).Error; err != nil {
l.Errorw("failed to update device",
logger.Field("identifier", deviceInfo.Identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err)
}
return nil
})
if err != nil {
l.Errorw("device rebinding failed",
logger.Field("identifier", deviceInfo.Identifier),
logger.Field("old_user_id", oldUserId),
logger.Field("new_user_id", newUserId),
logger.Field("error", err.Error()),
)
return err
}
l.Infow("device rebound successfully",
logger.Field("identifier", deviceInfo.Identifier),
logger.Field("old_user_id", oldUserId),
logger.Field("new_user_id", newUserId),
)
return nil
}
-293
View File
@@ -1,293 +0,0 @@
package auth
import (
"context"
"fmt"
"time"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/jwt"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/uuidx"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type DeviceLoginLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Device Login
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
return &DeviceLoginLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *types.LoginResponse, err error) {
if !l.svcCtx.Config.Device.Enable {
return nil, xerr.NewErrMsg("Device login is disabled")
}
loginStatus := false
var userInfo *user.User
// Record login status
defer func() {
if userInfo != nil && userInfo.Id != 0 {
loginLog := log.Login{
Method: "device",
LoginIP: req.IP,
UserAgent: req.UserAgent,
Success: loginStatus,
Timestamp: time.Now().UnixMilli(),
}
content, _ := loginLog.Marshal()
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"),
ObjectID: userInfo.Id,
Content: string(content),
}); err != nil {
l.Errorw("failed to insert login log",
logger.Field("user_id", userInfo.Id),
logger.Field("ip", req.IP),
logger.Field("error", err.Error()),
)
}
}
}()
// Check if device exists by identifier
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Device not found, create new user and device
userInfo, err = l.registerUserAndDevice(req)
if err != nil {
return nil, err
}
} else {
l.Errorw("query device failed",
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
}
} else {
// Device found, get user info
userInfo, err = l.svcCtx.UserModel.FindOne(l.ctx, deviceInfo.UserId)
if err != nil {
l.Errorw("query user failed",
logger.Field("user_id", deviceInfo.UserId),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user failed: %v", err.Error())
}
}
// Generate session id
sessionId := uuidx.NewUUID().String()
// Generate token
token, err := jwt.NewJwtToken(
l.svcCtx.Config.JwtAuth.AccessSecret,
time.Now().Unix(),
l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", "device"),
)
if err != nil {
l.Errorw("token generate error",
logger.Field("user_id", userInfo.Id),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
}
// Store session id in redis
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil {
l.Errorw("set session id error",
logger.Field("user_id", userInfo.Id),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error())
}
loginStatus = true
return &types.LoginResponse{
Token: token,
}, nil
}
func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) (*user.User, error) {
l.Infow("device not found, creating new user and device",
logger.Field("identifier", req.Identifier),
logger.Field("ip", req.IP),
)
var userInfo *user.User
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
// Create new user
userInfo = &user.User{
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
}
if err := db.Create(userInfo).Error; err != nil {
l.Errorw("failed to create user",
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
}
// Update refer code
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
l.Errorw("failed to update refer code",
logger.Field("user_id", userInfo.Id),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
}
// Create device auth method
authMethod := &user.AuthMethods{
UserId: userInfo.Id,
AuthType: "device",
AuthIdentifier: req.Identifier,
Verified: true,
}
if err := db.Create(authMethod).Error; err != nil {
l.Errorw("failed to create device auth method",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
}
// Insert device record
deviceInfo := &user.Device{
Ip: req.IP,
UserId: userInfo.Id,
UserAgent: req.UserAgent,
Identifier: req.Identifier,
Enabled: true,
Online: false,
}
if err := db.Create(deviceInfo).Error; err != nil {
l.Errorw("failed to insert device",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert device failed: %v", err)
}
// Activate trial if enabled
if l.svcCtx.Config.Register.EnableTrial {
if err := l.activeTrial(userInfo.Id, db); err != nil {
return err
}
}
return nil
})
if err != nil {
l.Errorw("device registration failed",
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return nil, err
}
l.Infow("device registration completed successfully",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("refer_code", userInfo.ReferCode),
)
// Register log
registerLog := log.Register{
AuthMethod: "device",
Identifier: req.Identifier,
RegisterIP: req.IP,
UserAgent: req.UserAgent,
Timestamp: time.Now().UnixMilli(),
}
content, _ := registerLog.Marshal()
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeRegister.Uint8(),
Date: time.Now().Format("2006-01-02"),
ObjectID: userInfo.Id,
Content: string(content),
}); err != nil {
l.Errorw("failed to insert register log",
logger.Field("user_id", userInfo.Id),
logger.Field("ip", req.IP),
logger.Field("error", err.Error()),
)
}
return userInfo, nil
}
func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error {
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe)
if err != nil {
l.Errorw("failed to find trial subscription template",
logger.Field("user_id", userId),
logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe),
logger.Field("error", err.Error()),
)
return err
}
startTime := time.Now()
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId))
subscribeUUID := uuidx.NewUUID().String()
userSub := &user.Subscribe{
UserId: userId,
OrderId: 0,
SubscribeId: sub.Id,
StartTime: startTime,
ExpireTime: expireTime,
Traffic: sub.Traffic,
Download: 0,
Upload: 0,
Token: subscribeToken,
UUID: subscribeUUID,
Status: 1,
}
if err := db.Create(userSub).Error; err != nil {
l.Errorw("failed to insert trial subscription",
logger.Field("user_id", userId),
logger.Field("error", err.Error()),
)
return err
}
l.Infow("trial subscription activated successfully",
logger.Field("user_id", userId),
logger.Field("subscribe_id", sub.Id),
logger.Field("expire_time", expireTime),
logger.Field("traffic", sub.Traffic),
)
return nil
}
+1 -19
View File
@@ -104,26 +104,9 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
// Update password // Update password
userInfo.Password = tool.EncodePassWord(req.Password) userInfo.Password = tool.EncodePassWord(req.Password)
userInfo.Algo = "default" if err := l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
if err = l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error())
} }
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail register if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -133,7 +116,6 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
+1 -19
View File
@@ -98,7 +98,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
if req.TelephoneCode == "" { if req.TelephoneCode == "" {
// Verify password // Verify password
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) { if !tool.VerifyPassWord(req.Password, userInfo.Password) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
} }
} else { } else {
@@ -124,23 +124,6 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
l.svcCtx.Redis.Del(l.ctx, cacheKey) l.svcCtx.Redis.Del(l.ctx, cacheKey)
} }
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail login if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -150,7 +133,6 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
@@ -78,27 +78,11 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
// Generate password // Generate password
pwd := tool.EncodePassWord(req.Password) pwd := tool.EncodePassWord(req.Password)
userInfo.Password = pwd userInfo.Password = pwd
userInfo.Algo = "default"
err = l.svcCtx.UserModel.Update(l.ctx, userInfo) err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error())
} }
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail register if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -108,7 +92,6 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error()))
@@ -107,7 +107,6 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
pwd := tool.EncodePassWord(req.Password) pwd := tool.EncodePassWord(req.Password)
userInfo := &user.User{ userInfo := &user.User{
Password: pwd, Password: pwd,
Algo: "default",
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
AuthMethods: []user.AuthMethods{ AuthMethods: []user.AuthMethods{
{ {
@@ -139,22 +138,6 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
} }
return nil return nil
}) })
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail register if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -164,7 +147,6 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
+1 -19
View File
@@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/config"
@@ -77,25 +76,9 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
} }
// Verify password // Verify password
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) { if !tool.VerifyPassWord(req.Password, userInfo.Password) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
} }
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail login if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -105,7 +88,6 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
-17
View File
@@ -90,7 +90,6 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
pwd := tool.EncodePassWord(req.Password) pwd := tool.EncodePassWord(req.Password)
userInfo := &user.User{ userInfo := &user.User{
Password: pwd, Password: pwd,
Algo: "default",
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
} }
if referer != nil { if referer != nil {
@@ -126,21 +125,6 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
} }
return nil return nil
}) })
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
// Don't fail register if device binding fails, just log the error
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id // Generate session id
sessionId := uuidx.NewUUID().String() sessionId := uuidx.NewUUID().String()
// Generate token // Generate token
@@ -150,7 +134,6 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
l.svcCtx.Config.JwtAuth.AccessExpire, l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId), jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
) )
if err != nil { if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
@@ -2,7 +2,6 @@ package common
import ( import (
"context" "context"
"encoding/json"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/internal/types"
@@ -68,10 +67,6 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
for _, method := range authMethods { for _, method := range authMethods {
if *method.Enabled { if *method.Enabled {
methods = append(methods, method.Method) methods = append(methods, method.Method)
if method.Method == "device" {
_ = json.Unmarshal([]byte(method.Config), &resp.Auth.Device)
resp.Auth.Device.Enable = true
}
} }
} }
resp.OAuthMethods = methods resp.OAuthMethods = methods
+6 -49
View File
@@ -3,11 +3,6 @@ package common
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"io"
"net"
"net/http"
"slices"
"strings"
"time" "time"
"github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/config"
@@ -68,52 +63,14 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) {
l.Logger.Error("[GetStatLogic] get server_addr failed: ", logger.Field("error", err.Error())) l.Logger.Error("[GetStatLogic] get server_addr failed: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server_addr failed: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server_addr failed: %v", err.Error())
} }
type apireq struct {
query string country := 0
fields string
} protocolDict := make(map[string]struct{})
type apiret struct {
CountryCode string `json:"countryCode"`
}
//map as dict
type void struct{}
var v void
country := make(map[string]void)
for c := range slices.Chunk(nodeaddr, 100) {
var batchreq []apireq
for _, addr := range c {
isAddr := net.ParseIP(addr)
if isAddr == nil {
ip, err := net.LookupIP(addr)
if err == nil && len(ip) > 0 {
batchreq = append(batchreq, apireq{query: ip[0].String(), fields: "countryCode"})
}
} else {
batchreq = append(batchreq, apireq{query: addr, fields: "countryCode"})
}
}
req, _ := json.Marshal(batchreq)
ret, err := http.Post("http://ip-api.com/batch", "application/json", strings.NewReader(string(req)))
if err == nil {
retBytes, err := io.ReadAll(ret.Body)
if err == nil {
var retStruct []apiret
err := json.Unmarshal(retBytes, &retStruct)
if err == nil {
for _, dat := range retStruct {
if dat.CountryCode != "" {
country[dat.CountryCode] = v
}
}
}
}
}
}
protocolDict := make(map[string]void)
var protocol []string var protocol []string
l.svcCtx.DB.Model(&server.Server{}).Where("enable = true").Pluck("protocol", &protocol) l.svcCtx.DB.Model(&server.Server{}).Where("enable = true").Pluck("protocol", &protocol)
for _, p := range protocol { for _, p := range protocol {
protocolDict[p] = v protocolDict[p] = struct{}{}
} }
protocol = nil protocol = nil
for p := range protocolDict { for p := range protocolDict {
@@ -122,7 +79,7 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) {
resp = &types.GetStatResponse{ resp = &types.GetStatResponse{
User: u, User: u,
Node: n, Node: n,
Country: int64(len(country)), Country: int64(country),
Protocol: protocol, Protocol: protocol,
OnlineDevice: l.svcCtx.DeviceManager.GetOnlineDeviceCount(), OnlineDevice: l.svcCtx.DeviceManager.GetOnlineDeviceCount(),
} }
-33
View File
@@ -1,33 +0,0 @@
package common
import (
"context"
"time"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type HeartbeatLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewHeartbeatLogic Heartbeat
func NewHeartbeatLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HeartbeatLogic {
return &HeartbeatLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *HeartbeatLogic) Heartbeat() (resp *types.HeartbeatResponse, err error) {
return &types.HeartbeatResponse{
Status: true,
Message: "service is alive",
Timestamp: time.Now().Unix(),
}, nil
}
+1 -1
View File
@@ -57,7 +57,7 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error {
return err return err
} }
// Verify sign // Verify sign
client := epay.NewClient(config.Pid, config.Url, config.Key, config.Type) client := epay.NewClient(config.Pid, config.Url, config.Key)
if !client.VerifySign(urlParamsToMap(l.ctx.Request.URL.RawQuery)) && !l.svcCtx.Config.Debug { if !client.VerifySign(urlParamsToMap(l.ctx.Request.URL.RawQuery)) && !l.svcCtx.Config.Debug {
l.Logger.Error("[EPayNotify] Verify sign failed") l.Logger.Error("[EPayNotify] Verify sign failed")
return nil return nil
@@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/report"
"github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/constant"
paymentPlatform "github.com/perfect-panel/server/pkg/payment" paymentPlatform "github.com/perfect-panel/server/pkg/payment"
@@ -268,7 +267,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
} }
// Initialize EPay client with merchant credentials // Initialize EPay client with merchant credentials
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type) client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key)
// Convert order amount to CNY using current exchange rate // Convert order amount to CNY using current exchange rate
amount, err := l.queryExchangeRate("CNY", info.Amount) amount, err := l.queryExchangeRate("CNY", info.Amount)
@@ -276,29 +275,16 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
return "", err return "", err
} }
// gateway mod
isGatewayMod := report.IsGatewayMode()
// Build notification URL for payment status callbacks // Build notification URL for payment status callbacks
notifyUrl := "" notifyUrl := ""
if config.Domain != "" { if config.Domain != "" {
notifyUrl = config.Domain notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token
if isGatewayMod {
notifyUrl += "/api/"
}
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
} else { } else {
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string) host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
if !ok { if !ok {
host = l.svcCtx.Config.Host host = l.svcCtx.Config.Host
} }
notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token
notifyUrl = "https://" + host
if isGatewayMod {
notifyUrl += "/api"
}
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
} }
// Create payment URL for user redirection // Create payment URL for user redirection
@@ -323,7 +309,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
} }
// Initialize EPay client with merchant credentials // Initialize EPay client with merchant credentials
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type) client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey)
// Convert order amount to CNY using current exchange rate // Convert order amount to CNY using current exchange rate
amount, err := l.queryExchangeRate("CNY", info.Amount) amount, err := l.queryExchangeRate("CNY", info.Amount)
@@ -331,29 +317,18 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
return "", err return "", err
} }
// gateway mod
isGatewayMod := report.IsGatewayMode()
// Build notification URL for payment status callbacks // Build notification URL for payment status callbacks
notifyUrl := "" notifyUrl := ""
if config.Domain != "" { if config.Domain != "" {
notifyUrl = config.Domain notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token
if isGatewayMod {
notifyUrl += "/api/"
}
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
} else { } else {
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string) host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
if !ok { if !ok {
host = l.svcCtx.Config.Host host = l.svcCtx.Config.Host
} }
notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token
notifyUrl = "https://" + host
if isGatewayMod {
notifyUrl += "/api"
}
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
} }
// Create payment URL for user redirection // Create payment URL for user redirection
url := client.CreatePayUrl(epay.Order{ url := client.CreatePayUrl(epay.Order{
Name: l.svcCtx.Config.Site.SiteName, Name: l.svcCtx.Config.Site.SiteName,
@@ -372,11 +347,6 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
// Convert cents to decimal amount // Convert cents to decimal amount
amount = float64(src) / float64(100) amount = float64(src) / float64(100)
if l.svcCtx.ExchangeRate != 0 && to == "CNY" {
amount = amount * l.svcCtx.ExchangeRate
return amount, nil
}
// Retrieve system currency configuration // Retrieve system currency configuration
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx) currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
if err != nil { if err != nil {
@@ -83,12 +83,6 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types.
if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used")
} }
// Check expiration time
expireTime := time.Unix(couponInfo.ExpireTime, 0)
if time.Now().After(expireTime) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponExpired), "coupon expired")
}
couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) couponSub := tool.StringToInt64Slice(couponInfo.Subscribe)
if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) { if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match")
@@ -58,12 +58,17 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
} }
list[i] = sub list[i] = sub
// 通过服务组查询关联的节点数量 // 通过服务组查询关联的节点数量
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, tool.StringToInt64Slice(sub.ServerGroup)) if item.ServerGroup != "" {
if err != nil { groupIds := tool.StringToInt64Slice(item.ServerGroup)
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error())) servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, groupIds)
sub.ServerCount = 0 if err != nil {
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
sub.ServerCount = 0
} else {
sub.ServerCount = int64(len(servers))
}
} else { } else {
sub.ServerCount = int64(len(servers)) sub.ServerCount = 0
} }
list[i] = sub list[i] = sub
} }
@@ -1,195 +0,0 @@
package subscribe
import (
"context"
"strings"
"time"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type QueryUserSubscribeNodeListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get user subscribe node info
func NewQueryUserSubscribeNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserSubscribeNodeListLogic {
return &QueryUserSubscribeNodeListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryUserSubscribeNodeListLogic) QueryUserSubscribeNodeList() (resp *types.QueryUserSubscribeNodeListResponse, err error) {
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
logger.Error("current user is not found in context")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
userSubscribes, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 1, 2)
if err != nil {
logger.Errorw("failed to query user subscribe", logger.Field("error", err.Error()), logger.Field("user_id", u.Id))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "DB_ERROR")
}
resp = &types.QueryUserSubscribeNodeListResponse{}
for _, us := range userSubscribes {
userSubscribe, err := l.getUserSubscribe(us.Token)
if err != nil {
l.Errorw("[SubscribeLogic] Get user subscribe failed", logger.Field("error", err.Error()), logger.Field("token", userSubscribe.Token))
return nil, err
}
nodes, err := l.getServers(userSubscribe)
if err != nil {
return nil, err
}
userSubscribeInfo := types.UserSubscribeInfo{
Id: userSubscribe.Id,
Nodes: nodes,
Traffic: userSubscribe.Traffic,
Upload: userSubscribe.Upload,
Download: userSubscribe.Download,
Token: userSubscribe.Token,
UserId: userSubscribe.UserId,
OrderId: userSubscribe.OrderId,
SubscribeId: userSubscribe.SubscribeId,
StartTime: userSubscribe.StartTime.Unix(),
ExpireTime: userSubscribe.ExpireTime.Unix(),
Status: userSubscribe.Status,
CreatedAt: userSubscribe.CreatedAt.Unix(),
UpdatedAt: userSubscribe.UpdatedAt.Unix(),
}
if userSubscribe.FinishedAt != nil {
userSubscribeInfo.FinishedAt = userSubscribe.FinishedAt.Unix()
}
if l.svcCtx.Config.Register.EnableTrial && l.svcCtx.Config.Register.TrialSubscribe == userSubscribe.SubscribeId {
userSubscribeInfo.IsTryOut = true
}
resp.List = append(resp.List, userSubscribeInfo)
}
return
}
func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (userSubscribeNodes []*types.UserSubscribeNodeInfo, err error) {
userSubscribeNodes = make([]*types.UserSubscribeNodeInfo, 0)
if l.isSubscriptionExpired(userSub) {
return l.createExpiredServers(), nil
}
subDetails, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, userSub.SubscribeId)
if err != nil {
l.Errorw("[Generate Subscribe]find subscribe details error: %v", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error())
}
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
tags := strings.Split(subDetails.NodeTags, ",")
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
enable := true
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: 0,
Size: 1000,
NodeId: nodeIds,
Enabled: &enable, // Only get enabled nodes
})
if len(nodes) > 0 {
var serverMapIds = make(map[int64]*node.Server)
for _, n := range nodes {
serverMapIds[n.ServerId] = nil
}
var serverIds []int64
for k := range serverMapIds {
serverIds = append(serverIds, k)
}
servers, err := l.svcCtx.NodeModel.QueryServerList(l.ctx, serverIds)
if err != nil {
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
}
for _, s := range servers {
serverMapIds[s.Id] = s
}
for _, n := range nodes {
server := serverMapIds[n.ServerId]
if server == nil {
continue
}
userSubscribeNode := &types.UserSubscribeNodeInfo{
Id: n.Id,
Name: n.Name,
Uuid: userSub.UUID,
Protocol: n.Protocol,
Port: n.Port,
Address: n.Address,
Tags: strings.Split(n.Tags, ","),
Country: server.Country,
City: server.City,
CreatedAt: n.CreatedAt.Unix(),
}
userSubscribeNodes = append(userSubscribeNodes, userSubscribeNode)
}
}
l.Debugf("[Query Subscribe]found servers: %v", len(nodes))
if err != nil {
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
}
logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes))
return userSubscribeNodes, nil
}
func (l *QueryUserSubscribeNodeListLogic) isSubscriptionExpired(userSub *user.Subscribe) bool {
return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0
}
func (l *QueryUserSubscribeNodeListLogic) createExpiredServers() []*types.UserSubscribeNodeInfo {
return nil
}
func (l *QueryUserSubscribeNodeListLogic) getFirstHostLine() string {
host := l.svcCtx.Config.Host
lines := strings.Split(host, "\n")
if len(lines) > 0 {
return lines[0]
}
return host
}
func (l *QueryUserSubscribeNodeListLogic) getUserSubscribe(token string) (*user.Subscribe, error) {
userSub, err := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, token)
if err != nil {
l.Infow("[Generate Subscribe]find subscribe error: %v", logger.Field("error", err.Error()), logger.Field("token", token))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
}
// Ignore expiration check
//if userSub.Status > 1 {
// l.Infow("[Generate Subscribe]subscribe is not available", logger.Field("status", int(userSub.Status)), logger.Field("token", token))
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNotAvailable), "subscribe is not available")
//}
return userSub, nil
}
@@ -41,6 +41,7 @@ func (l *CreateUserTicketLogic) CreateUserTicket(req *types.CreateUserTicketRequ
Description: req.Description, Description: req.Description,
UserId: u.Id, UserId: u.Id,
Status: ticket.Pending, Status: ticket.Pending,
IssueType: req.IssueType,
}) })
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert ticket error: %v", err.Error()) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert ticket error: %v", err.Error())
@@ -36,7 +36,7 @@ func (l *GetUserTicketListLogic) GetUserTicketList(req *types.GetUserTicketListR
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
} }
l.Logger.Debugf("Current user: %v", u.Id) l.Logger.Debugf("Current user: %v", u.Id)
total, list, err := l.svcCtx.TicketModel.QueryTicketList(l.ctx, req.Page, req.Size, u.Id, req.Status, req.Search) total, list, err := l.svcCtx.TicketModel.QueryTicketList(l.ctx, req.Page, req.Size, u.Id, req.Status, req.IssueType, req.Search)
if err != nil { if err != nil {
l.Errorw("[GetUserTicketListLogic] Database Error", logger.Field("error", err.Error())) l.Errorw("[GetUserTicketListLogic] Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryTicketList error: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryTicketList error: %v", err)
@@ -1,108 +0,0 @@
package user
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CommissionWithdrawLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Commission Withdraw
func NewCommissionWithdrawLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommissionWithdrawLogic {
return &CommissionWithdrawLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CommissionWithdrawLogic) CommissionWithdraw(req *types.CommissionWithdrawRequest) (resp *types.WithdrawalLog, err error) {
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
logger.Error("current user is not found in context")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
if u.Commission < req.Amount {
logger.Errorf("User %d has insufficient commission balance: %.2f, requested: %.2f", u.Id, float64(u.Commission)/100, float64(req.Amount)/100)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserCommissionNotEnough), "User %d has insufficient commission balance", u.Id)
}
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
// update user commission balance
u.Commission -= req.Amount
if err = l.svcCtx.UserModel.Update(l.ctx, u, tx); err != nil {
tx.Rollback()
l.Errorf("Failed to update user %d commission balance: %v", u.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update user %d commission balance: %v", u.Id, err)
}
// create withdrawal log
logInfo := log.Commission{
Type: log.CommissionTypeConvertBalance,
Amount: req.Amount,
Timestamp: time.Now().UnixMilli(),
}
b, err := logInfo.Marshal()
if err != nil {
tx.Rollback()
l.Errorf("Failed to marshal commission log for user %d: %v", u.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to marshal commission log for user %d: %v", u.Id, err)
}
err = tx.Model(log.SystemLog{}).Create(&log.SystemLog{
Type: log.TypeCommission.Uint8(),
Date: time.Now().Format("2006-01-02"),
ObjectID: u.Id,
Content: string(b),
CreatedAt: time.Now(),
}).Error
if err != nil {
tx.Rollback()
l.Errorf("Failed to create commission log for user %d: %v", u.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create commission log for user %d: %v", u.Id, err)
}
err = tx.Model(&user.Withdrawal{}).Create(&user.Withdrawal{
UserId: u.Id,
Amount: req.Amount,
Content: req.Content,
Status: 0,
Reason: "",
}).Error
if err != nil {
tx.Rollback()
l.Errorf("Failed to create withdrawal log for user %d: %v", u.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create withdrawal log for user %d: %v", u.Id, err)
}
if err = tx.Commit().Error; err != nil {
l.Errorf("Transaction commit failed for user %d withdrawal: %v", u.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Transaction commit failed for user %d withdrawal: %v", u.Id, err)
}
return &types.WithdrawalLog{
UserId: u.Id,
Amount: req.Amount,
Content: req.Content,
Status: 0,
Reason: "",
CreatedAt: time.Now().UnixMilli(),
}, nil
}
@@ -1,39 +0,0 @@
package user
import (
"context"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
)
type GetDeviceListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get Device List
func NewGetDeviceListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetDeviceListLogic {
return &GetDeviceListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetDeviceListLogic) GetDeviceList() (resp *types.GetDeviceListResponse, err error) {
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
list, count, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, userInfo.Id)
userRespList := make([]types.UserDevice, 0)
tool.DeepCopy(&userRespList, list)
resp = &types.GetDeviceListResponse{
Total: count,
List: userRespList,
}
return
}
@@ -29,7 +29,7 @@ func NewQueryUserBalanceLogLogic(ctx context.Context, svcCtx *svc.ServiceContext
} }
} }
func (l *QueryUserBalanceLogLogic) QueryUserBalanceLog() (resp *types.QueryUserBalanceLogListResponse, err error) { func (l *QueryUserBalanceLogLogic) QueryUserBalanceLog(req *types.QueryUserBalanceLogListRequest) (resp *types.QueryUserBalanceLogListResponse, err error) {
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok { if !ok {
logger.Error("current user is not found in context") logger.Error("current user is not found in context")
@@ -60,8 +60,25 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub
} }
} }
short, _ := tool.FixedUniqueString(item.Token, 8, "") // 计算节点数量(通过服务组关联的实际节点数量)
sub.Short = short if item.Subscribe != nil {
// 获取服务组ID列表
groupIds := tool.StringToInt64Slice(item.Subscribe.ServerGroup)
// 通过服务组查询关联的节点数量
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, groupIds)
if err != nil {
l.Errorw("[QueryUserSubscribeLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
sub.Subscribe.ServerCount = 0
} else {
sub.Subscribe.ServerCount = int64(len(servers))
}
// 保留原始服务器ID列表用于其他用途
serverIds := tool.StringToInt64Slice(item.Subscribe.Server)
sub.Subscribe.Server = serverIds
}
sub.ResetTime = calculateNextResetTime(&sub) sub.ResetTime = calculateNextResetTime(&sub)
resp.List = append(resp.List, sub) resp.List = append(resp.List, sub)
} }

Some files were not shown because too many files have changed in this diff Show More