This commit is contained in:
shanshanzhong 2025-10-10 07:13:36 -07:00
commit 95c66c0a8a
1023 changed files with 78353 additions and 0 deletions

3
.commitlintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.idea/
.vscode/
*-dev.yaml
*.local.yaml
/test/
*.log
.DS_Store
*_test_config.go
/build/
etc/ppanel.yaml
*.p8
*.crt
*.key
node_modules
package-lock.json
package.json

66
.goreleaser.yaml Normal file
View File

@ -0,0 +1,66 @@
project_name: ppanel
version: 1
release:
prerelease: auto
builds:
- # If true, skip the build.
# Useful for library projects.
# Default is false
skip: true
changelog:
# Set it to true if you wish to skip the changelog generation.
# This may result in an empty release notes on GitHub/GitLab/Gitea.
disable: false
# Changelog generation implementation to use.
#
# Valid options are:
# - `git`: uses `git log`;
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
#
# Defaults to `git`.
use: github
# Sorts the changelog by the commit's messages.
# Could either be asc, desc or empty
# Default is empty
sort: asc
# Format to use for commit formatting.
# Only available when use is one of `github`, `gitea`, or `gitlab`.
#
# Default: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'.
# Extra template fields: `SHA`, `Message`, `AuthorName`, `AuthorEmail`, and
# `AuthorUsername`.
format: "{{ .Message }}"
# Group commits messages by given regex and title.
# Order value defines the order of the groups.
# Proving no regex means all commits will be grouped under the default group.
# Groups are disabled when using github-native, as it already groups things by itself.
#
# Default is no groups.
groups:
- title: "✨ Features"
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- title: "🐛 Bug Fixes"
regexp: "^.*fix[(\\w)]*:+.*$"
order: 1
- title: "🎫 Chores"
regexp: "^.*chore[(\\w)]*:+.*$"
order: 2
- title: "🔨 Refactor"
regexp: "^.*refactor[(\\w)]*:+.*$"
order: 3
- title: "🔧 Build"
regexp: "^.*?(ci)(\\(.+\\))??!?:.+$"
order: 4
- title: "📝 Documentation"
regexp: "^.*?docs?(\\(.+\\))??!?:.+$"
order: 5
- title: "✨ Others"
order: 999

40
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,40 @@
# Pull Request Submission Guidelines
To ensure the quality of the codebase and maintainability of the project, please follow these guidelines before submitting a Pull Request (PR):
## 1. PR Title and Description
- **Clear Title**: Concisely describe the main content of the PR, for example:
- Fix: Correct error messages in user login
- Feature: Add order export functionality
- **Detailed Description**: Include the following details in the description:
- Purpose and background of this PR.
- Detailed explanation of the changes.
- For bug fixes, describe the steps to reproduce the issue.
- For new features, explain how to use them.
- Link related issues (if any) using keywords like `Closes #123`.
## 2. Code Checks Before Submission
- **Code Style**: Ensure the code adheres to the project's coding standards (e.g., ESLint, Prettier, or GoLint).
- **Functional Testing**: Fully test new features or bug fixes to ensure no missing functionality or regressions.
- **Unit Tests**: Write unit tests for added or modified functionality and ensure all tests pass.
- **Documentation Updates**: Update documentation if the PR includes new features or API changes.
## 3. Branch Strategy
- **Correct Branch**:
- Develop new features based on `feature/*` branches.
- Fix bugs based on `fix/*` branches.
- Ensure the target branch of the PR aligns with the project's branching strategy.
- **Sync with Base Branch**: Before submitting the PR, ensure your branch is up-to-date with the target branch (e.g., `main` or `develop`).
## 4. Review Process
- **Small Commits**: Avoid submitting excessive changes in a single PR; break it into smaller logical units.
---
Thank you for your contribution!

44
CONTRIBUTING_ZH.md Normal file
View File

@ -0,0 +1,44 @@
# Pull Request 提交须知
为了确保代码库的质量和项目的可维护性,在提交 Pull RequestPR之前请务必遵循以下准则
## 1. PR 标题和描述
- **标题清晰**:简明扼要地描述 PR 的主要内容,例如:
- Fix: 修复用户登录时的错误提示
- Feature: 添加订单导出功能
- **描述详细**:在描述中包括以下内容:
- 此 PR 的目的和背景。
- 变更的详细说明。
- 如果涉及 Bug 修复,需描述问题重现的步骤。
- 如果涉及新功能,需描述其使用方式。
- 关联的 Issue如有使用关键字关闭 Issue例如`Closes #123`
## 2. 提交代码前的检查
- **代码风格**:确保代码符合项目的代码规范。
- **功能测试**:对新功能或 Bug 修复进行全面测试,确保没有功能缺失或回归问题。
- **单元测试**:为新增或修改的功能编写单元测试,并确保所有测试通过(工具类即`pkg/*`下面的必须带有单元测试)。
- **文档更新**:如果 PR 涉及新功能或接口更改,确保文档同步更新。
## 3. 分支策略
- **正确的分支**
- 新功能应基于 `feature/*` 分支进行开发。
- Bug 修复应基于 `fix/*` 分支。
- 确保 PR 的目标分支与项目的分支策略一致。
- **同步主干代码**:在提交 PR 之前请确保分支已经与目标分支develop同步。
## 4. 审查流程
- **小型提交**:避免一次性提交过多的更改,将 PR 拆分为更小的逻辑单元。
---
感谢您的贡献!

44
Dockerfile Normal file
View File

@ -0,0 +1,44 @@
# Use a smaller base image for the build stage
FROM golang:alpine AS builder
LABEL stage=gobuilder
ARG TARGETARCH
ENV CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH}
# Combine apk commands into one to reduce layer size
RUN apk update --no-cache && apk add --no-cache tzdata ca-certificates
WORKDIR /build
# Copy go.mod and go.sum first to take advantage of Docker caching
COPY go.mod go.sum ./
RUN go mod download
# Copy the rest of the application code
COPY . .
# Build the binary with optimization flags to reduce binary size
RUN go build -ldflags="-s -w" -o /app/ppanel ppanel.go
# Final minimal image
FROM scratch
# Copy CA certificates and timezone data
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
ENV TZ=Asia/Shanghai
# Set working directory and copy binary
WORKDIR /app
COPY --from=builder /app/ppanel /app/ppanel
COPY --from=builder /etc /app/etc
# Expose the port (optional)
EXPOSE 8080
# Specify entry point
ENTRYPOINT ["/app/ppanel"]
CMD ["run", "--config", "etc/ppanel.yaml"]

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

79
apis/admin/ads.api Normal file
View File

@ -0,0 +1,79 @@
syntax = "v1"
info (
title: "Ads API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
type (
CreateAdsRequest {
Title string `json:"title"`
Type string `json:"type"`
Content string `json:"content"`
Description string `json:"description"`
TargetURL string `json:"target_url"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Status int `json:"status"`
}
UpdateAdsRequest {
Id int64 `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Content string `json:"content"`
Description string `json:"description"`
TargetURL string `json:"target_url"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Status int `json:"status"`
}
DeleteAdsRequest {
Id int64 `json:"id"`
}
GetAdsListRequest {
Page int `form:"page"`
Size int `form:"size"`
Status *int `form:"status,omitempty"`
Search string `form:"search,omitempty"`
}
GetAdsListResponse {
Total int64 `json:"total"`
List []Ads `json:"list"`
}
GetAdsDetailRequest {
Id int64 `form:"id"`
}
)
import "../types.api"
@server (
prefix: v1/admin/ads
group: admin/ads
middleware: AuthMiddleware
)
service ppanel {
@doc "Create Ads"
@handler CreateAds
post / (CreateAdsRequest)
@doc "Update Ads"
@handler UpdateAds
put / (UpdateAdsRequest)
@doc "Delete Ads"
@handler DeleteAds
delete / (DeleteAdsRequest)
@doc "Get Ads List"
@handler GetAdsList
get /list (GetAdsListRequest) returns (GetAdsListResponse)
@doc "Get Ads Detail"
@handler GetAdsDetail
get /detail (GetAdsDetailRequest) returns (Ads)
}

View File

@ -0,0 +1,76 @@
syntax = "v1"
info (
title: "Announcement API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
CreateAnnouncementRequest {
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
}
UpdateAnnouncementRequest {
Id int64 `json:"id" validate:"required"`
Title string `json:"title"`
Content string `json:"content"`
Show *bool `json:"show"`
Pinned *bool `json:"pinned"`
Popup *bool `json:"popup"`
}
UpdateAnnouncementEnableRequest {
Id int64 `json:"id" validate:"required"`
Enable *bool `json:"enable" validate:"required"`
}
DeleteAnnouncementRequest {
Id int64 `json:"id" validate:"required"`
}
GetAnnouncementListRequest {
Page int64 `form:"page"`
Size int64 `form:"size"`
Show *bool `form:"show,omitempty"`
Pinned *bool `form:"pinned,omitempty"`
Popup *bool `form:"popup,omitempty"`
Search string `form:"search,omitempty"`
}
GetAnnouncementListResponse {
Total int64 `json:"total"`
List []Announcement `json:"list"`
}
GetAnnouncementRequest {
Id int64 `form:"id" validate:"required"`
}
)
@server (
prefix: v1/admin/announcement
group: admin/announcement
middleware: AuthMiddleware
)
service ppanel {
@doc "Create announcement"
@handler CreateAnnouncement
post / (CreateAnnouncementRequest)
@doc "Update announcement"
@handler UpdateAnnouncement
put / (UpdateAnnouncementRequest)
@doc "Get announcement list"
@handler GetAnnouncementList
get /list (GetAnnouncementListRequest) returns (GetAnnouncementListResponse)
@doc "Delete announcement"
@handler DeleteAnnouncement
delete / (DeleteAnnouncementRequest)
@doc "Get announcement"
@handler GetAnnouncement
get /detail (GetAnnouncementRequest) returns (Announcement)
}

76
apis/admin/auth.api Normal file
View File

@ -0,0 +1,76 @@
syntax = "v1"
info (
title: "Auth Method Management"
desc: "System auth method management"
author: "Tension"
email: "tension@ppanel.com"
version: "0.1.2"
)
import (
"../types.api"
)
type (
UpdateAuthMethodConfigRequest {
Id int64 `json:"id"`
Method string `json:"method"`
Config interface{} `json:"config"`
Enabled *bool `json:"enabled"`
}
GetAuthMethodConfigRequest {
Method string `form:"method"`
}
GetAuthMethodListResponse {
List []AuthMethodConfig `json:"list"`
}
TestSmsSendRequest {
AreaCode string `json:"area_code" validate:"required"`
Telephone string `json:"telephone" validate:"required"`
}
// Test email smtp request
TestEmailSendRequest {
Email string `json:"email" validate:"required"`
}
)
@server (
prefix: v1/admin/auth-method
group: admin/authMethod
middleware: AuthMiddleware
)
service ppanel {
@doc "Get auth method list"
@handler GetAuthMethodList
get /list returns (GetAuthMethodListResponse)
@doc "Get auth method config"
@handler GetAuthMethodConfig
get /config (GetAuthMethodConfigRequest) returns (AuthMethodConfig)
@doc "Update auth method config"
@handler UpdateAuthMethodConfig
put /config (UpdateAuthMethodConfigRequest) returns (AuthMethodConfig)
@doc "Test sms send"
@handler TestSmsSend
post /test_sms_send (TestSmsSendRequest)
@doc "Test email send"
@handler TestEmailSend
post /test_email_send (TestEmailSendRequest)
@doc "Get sms support platform"
@handler GetSmsPlatform
get /sms_platform returns (PlatformResponse)
@doc "Get email support platform"
@handler GetEmailPlatform
get /email_platform returns (PlatformResponse)
}

88
apis/admin/console.api Normal file
View File

@ -0,0 +1,88 @@
syntax = "v1"
info (
title: "Console API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
type (
ServerTrafficData {
ServerId int64 `json:"server_id"`
Name string `json:"name"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
}
UserTrafficData {
SID int64 `json:"sid"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
}
ServerTotalDataResponse {
OnlineUserIPs int64 `json:"online_user_ips"`
OnlineServers int64 `json:"online_servers"`
OfflineServers int64 `json:"offline_servers"`
TodayUpload int64 `json:"today_upload"`
TodayDownload int64 `json:"today_download"`
MonthlyUpload int64 `json:"monthly_upload"`
MonthlyDownload int64 `json:"monthly_download"`
UpdatedAt int64 `json:"updated_at"`
ServerTrafficRankingToday []ServerTrafficData `json:"server_traffic_ranking_today"`
ServerTrafficRankingYesterday []ServerTrafficData `json:"server_traffic_ranking_yesterday"`
UserTrafficRankingToday []UserTrafficData `json:"user_traffic_ranking_today"`
UserTrafficRankingYesterday []UserTrafficData `json:"user_traffic_ranking_yesterday"`
}
UserStatistics {
Date string `json:"date,omitempty"`
Register int64 `json:"register"`
NewOrderUsers int64 `json:"new_order_users"`
RenewalOrderUsers int64 `json:"renewal_order_users"`
List []UserStatistics `json:"list,omitempty"`
}
OrdersStatistics {
Date string `json:"date,omitempty"`
AmountTotal int64 `json:"amount_total"`
NewOrderAmount int64 `json:"new_order_amount"`
RenewalOrderAmount int64 `json:"renewal_order_amount"`
List []OrdersStatistics `json:"list,omitempty"`
}
RevenueStatisticsResponse {
Today OrdersStatistics `json:"today"`
Monthly OrdersStatistics `json:"monthly"`
All OrdersStatistics `json:"all"`
}
UserStatisticsResponse {
Today UserStatistics `json:"today"`
Monthly UserStatistics `json:"monthly"`
All UserStatistics `json:"all"`
}
TicketWaitRelpyResponse {
Count int64 `json:"count"`
}
)
@server (
prefix: v1/admin/console
group: admin/console
middleware: AuthMiddleware
)
service ppanel {
@doc "Query server total data"
@handler QueryServerTotalData
get /server returns (ServerTotalDataResponse)
@doc "Query revenue statistics"
@handler QueryRevenueStatistics
get /revenue returns (RevenueStatisticsResponse)
@doc "Query user statistics"
@handler QueryUserStatistics
get /user returns (UserStatisticsResponse)
@doc "Query ticket wait reply"
@handler QueryTicketWaitReply
get /ticket returns (TicketWaitRelpyResponse)
}

85
apis/admin/coupon.api Normal file
View File

@ -0,0 +1,85 @@
syntax = "v1"
info (
title: "coupon API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
CreateCouponRequest {
Name string `json:"name" validate:"required"`
Code string `json:"code,omitempty"`
Count int64 `json:"count,omitempty"`
Type uint8 `json:"type" validate:"required"`
Discount int64 `json:"discount" validate:"required"`
StartTime int64 `json:"start_time" validate:"required"`
ExpireTime int64 `json:"expire_time" validate:"required"`
UserLimit int64 `json:"user_limit,omitempty"`
Subscribe []int64 `json:"subscribe,omitempty"`
UsedCount int64 `json:"used_count,omitempty"`
Enable *bool `json:"enable,omitempty"`
}
UpdateCouponRequest {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Code string `json:"code,omitempty"`
Count int64 `json:"count,omitempty"`
Type uint8 `json:"type" validate:"required"`
Discount int64 `json:"discount" validate:"required"`
StartTime int64 `json:"start_time" validate:"required"`
ExpireTime int64 `json:"expire_time" validate:"required"`
UserLimit int64 `json:"user_limit,omitempty"`
Subscribe []int64 `json:"subscribe,omitempty"`
UsedCount int64 `json:"used_count,omitempty"`
Enable *bool `json:"enable,omitempty"`
}
DeleteCouponRequest {
Id int64 `json:"id" validate:"required"`
}
BatchDeleteCouponRequest {
Ids []int64 `json:"ids" validate:"required"`
}
GetCouponListRequest {
Page int64 `form:"page" validate:"required"`
Size int64 `form:"size" validate:"required"`
Subscribe int64 `form:"subscribe,omitempty"`
Search string `form:"search,omitempty"`
}
GetCouponListResponse {
Total int64 `json:"total"`
List []Coupon `json:"list"`
}
)
@server (
prefix: v1/admin/coupon
group: admin/coupon
middleware: AuthMiddleware
)
service ppanel {
@doc "Create coupon"
@handler CreateCoupon
post / (CreateCouponRequest)
@doc "Update coupon"
@handler UpdateCoupon
put / (UpdateCouponRequest)
@doc "Delete coupon"
@handler DeleteCoupon
delete / (DeleteCouponRequest)
@doc "Batch delete coupon"
@handler BatchDeleteCoupon
delete /batch (BatchDeleteCouponRequest)
@doc "Get coupon list"
@handler GetCouponList
get /list (GetCouponListRequest) returns (GetCouponListResponse)
}

13
apis/admin/device.api Normal file
View File

@ -0,0 +1,13 @@
syntax = "v1"
info(
title: "Device API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
type (
)

78
apis/admin/document.api Normal file
View File

@ -0,0 +1,78 @@
syntax = "v1"
info (
title: "Document API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
CreateDocumentRequest {
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
Tags []string `json:"tags,omitempty" `
Show *bool `json:"show"`
}
UpdateDocumentRequest {
Id int64 `json:"id" validate:"required"`
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
Tags []string `json:"tags,omitempty" `
Show *bool `json:"show"`
}
DeleteDocumentRequest {
Id int64 `json:"id" validate:"required"`
}
BatchDeleteDocumentRequest {
Ids []int64 `json:"ids" validate:"required"`
}
GetDocumentListRequest {
Page int64 `form:"page" validate:"required"`
Size int64 `form:"size" validate:"required"`
Tag string `form:"tag,omitempty"`
Search string `form:"search,omitempty"`
}
GetDocumentListResponse {
Total int64 `json:"total"`
List []Document `json:"list"`
}
GetDocumentDetailRequest {
Id int64 `json:"id" validate:"required"`
}
)
@server (
prefix: v1/admin/document
group: admin/document
middleware: AuthMiddleware
)
service ppanel {
@doc "Create document"
@handler CreateDocument
post / (CreateDocumentRequest)
@doc "Update document"
@handler UpdateDocument
put / (UpdateDocumentRequest)
@doc "Delete document"
@handler DeleteDocument
delete / (DeleteDocumentRequest)
@doc "Batch delete document"
@handler BatchDeleteDocument
delete /batch (BatchDeleteDocumentRequest)
@doc "Get document list"
@handler GetDocumentList
get /list (GetDocumentListRequest) returns (GetDocumentListResponse)
@doc "Get document detail"
@handler GetDocumentDetail
get /detail (GetDocumentDetailRequest) returns (Document)
}

40
apis/admin/log.api Normal file
View File

@ -0,0 +1,40 @@
syntax = "v1"
info (
title: "Log API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
GetMessageLogListRequest {
Page int `form:"page"`
Size int `form:"size"`
Type string `form:"type"`
Platform string `form:"platform,omitempty"`
To string `form:"to,omitempty"`
Subject string `form:"subject,omitempty"`
Content string `form:"content,omitempty"`
Status int `form:"status,omitempty"`
}
GetMessageLogListResponse {
Total int64 `json:"total"`
List []MessageLog `json:"list"`
}
)
@server (
prefix: v1/admin/log
group: admin/log
middleware: AuthMiddleware
)
service ppanel {
@doc "Get message log list"
@handler GetMessageLogList
get /message/list (GetMessageLogListRequest) returns (GetMessageLogListResponse)
}

68
apis/admin/order.api Normal file
View File

@ -0,0 +1,68 @@
syntax = "v1"
info (
title: "order API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
CreateOrderRequest {
UserId int64 `json:"user_id" validate:"required"`
Type uint8 `json:"type" validate:"required"`
Quantity int64 `json:"quantity,omitempty"`
Price int64 `json:"price" validate:"required"`
Amount int64 `json:"amount" validate:"required"`
Discount int64 `json:"discount,omitempty"`
Coupon string `json:"coupon,omitempty"`
CouponDiscount int64 `json:"coupon_discount,omitempty"`
Commission int64 `json:"commission"`
FeeAmount int64 `json:"fee_amount" validate:"required"`
PaymentId int64 `json:"payment_id" validate:"required"`
TradeNo string `json:"trade_no,omitempty"`
Status uint8 `json:"status,omitempty"`
SubscribeId int64 `json:"subscribe_id,omitempty"`
}
UpdateOrderStatusRequest {
Id int64 `json:"id" validate:"required"`
Status uint8 `json:"status" validate:"required"`
PaymentId int64 `json:"payment_id,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
GetOrderListRequest {
Page int64 `form:"page" validate:"required"`
Size int64 `form:"size" validate:"required"`
UserId int64 `form:"user_id,omitempty"`
Status uint8 `form:"status,omitempty"`
SubscribeId int64 `form:"subscribe_id,omitempty"`
Search string `form:"search,omitempty"`
}
GetOrderListResponse {
Total int64 `json:"total"`
List []Order `json:"list"`
}
)
@server (
prefix: v1/admin/order
group: admin/order
middleware: AuthMiddleware
)
service ppanel {
@doc "Create order"
@handler CreateOrder
post / (CreateOrderRequest)
@doc "Get order list"
@handler GetOrderList
get /list (GetOrderListRequest) returns (GetOrderListResponse)
@doc "Update order status"
@handler UpdateOrderStatus
put /status (UpdateOrderStatusRequest)
}

81
apis/admin/payment.api Normal file
View File

@ -0,0 +1,81 @@
syntax = "v1"
info (
title: "payment API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
CreatePaymentMethodRequest {
Name string `json:"name" validate:"required"`
Platform string `json:"platform" validate:"required"`
Description string `json:"description"`
Icon string `json:"icon,omitempty"`
Domain string `json:"domain,omitempty"`
Config interface{} `json:"config" validate:"required"`
FeeMode uint `json:"fee_mode"`
FeePercent int64 `json:"fee_percent,omitempty"`
FeeAmount int64 `json:"fee_amount,omitempty"`
Enable *bool `json:"enable" validate:"required"`
}
UpdatePaymentMethodRequest {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Platform string `json:"platform" validate:"required"`
Description string `json:"description"`
Icon string `json:"icon,omitempty"`
Domain string `json:"domain,omitempty"`
Config interface{} `json:"config" validate:"required"`
FeeMode uint `json:"fee_mode"`
FeePercent int64 `json:"fee_percent,omitempty"`
FeeAmount int64 `json:"fee_amount,omitempty"`
Enable *bool `json:"enable" validate:"required"`
}
DeletePaymentMethodRequest {
Id int64 `json:"id" validate:"required"`
}
GetPaymentMethodListRequest {
Page int `form:"page"`
Size int `form:"size"`
Platform string `form:"platform,omitempty"`
Search string `form:"search,omitempty"`
Enable *bool `form:"enable,omitempty"`
}
GetPaymentMethodListResponse {
Total int64 `json:"total"`
List []PaymentMethodDetail `json:"list"`
}
)
@server (
prefix: v1/admin/payment
group: admin/payment
middleware: AuthMiddleware
)
service ppanel {
@doc "Create Payment Method"
@handler CreatePaymentMethod
post / (CreatePaymentMethodRequest) returns (PaymentConfig)
@doc "Update Payment Method"
@handler UpdatePaymentMethod
put / (UpdatePaymentMethodRequest) returns (PaymentConfig)
@doc "Delete Payment Method"
@handler DeletePaymentMethod
delete / (DeletePaymentMethodRequest)
@doc "Get Payment Method List"
@handler GetPaymentMethodList
get /list (GetPaymentMethodListRequest) returns (GetPaymentMethodListResponse)
@doc "Get supported payment platform"
@handler GetPaymentPlatform
get /platform returns (PlatformResponse)
}

190
apis/admin/server.api Normal file
View File

@ -0,0 +1,190 @@
syntax = "v1"
info (
title: "Node API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
GetNodeServerListRequest {
Page int `form:"page" validate:"required"`
Size int `form:"size" validate:"required"`
Tag string `form:"tag,omitempty"`
GroupId int64 `form:"group_id,omitempty"`
Search string `form:"search,omitempty"`
}
GetNodeServerListResponse {
Total int64 `json:"total"`
List []Server `json:"list"`
}
UpdateNodeRequest {
Id int64 `json:"id" validate:"required"`
Tags []string `json:"tags"`
Country string `json:"country"`
City string `json:"city"`
Name string `json:"name" validate:"required"`
ServerAddr string `json:"server_addr" validate:"required"`
RelayMode string `json:"relay_mode"`
RelayNode []NodeRelay `json:"relay_node"`
SpeedLimit int `json:"speed_limit"`
TrafficRatio float32 `json:"traffic_ratio"`
GroupId int64 `json:"group_id"`
Protocol string `json:"protocol" validate:"required"`
Config interface{} `json:"config" validate:"required"`
Enable *bool `json:"enable"`
Sort int64 `json:"sort"`
}
CreateNodeRequest {
Name string `json:"name" validate:"required"`
Tags []string `json:"tags"`
Country string `json:"country"`
City string `json:"city"`
ServerAddr string `json:"server_addr" validate:"required"`
RelayMode string `json:"relay_mode"`
RelayNode []NodeRelay `json:"relay_node"`
SpeedLimit int `json:"speed_limit"`
TrafficRatio float32 `json:"traffic_ratio"`
GroupId int64 `json:"group_id"`
Protocol string `json:"protocol" validate:"required"`
Config interface{} `json:"config" validate:"required"`
Enable *bool `json:"enable"`
Sort int64 `json:"sort"`
}
DeleteNodeRequest {
Id int64 `json:"id" validate:"required"`
}
GetNodeGroupListResponse {
Total int64 `json:"total"`
List []ServerGroup `json:"list"`
}
CreateNodeGroupRequest {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
}
UpdateNodeGroupRequest {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Description string `json:"description"`
}
DeleteNodeGroupRequest {
Id int64 `json:"id" validate:"required"`
}
BatchDeleteNodeRequest {
Ids []int64 `json:"ids" validate:"required"`
}
BatchDeleteNodeGroupRequest {
Ids []int64 `json:"ids" validate:"required"`
}
GetNodeDetailRequest {
Id int64 `form:"id" validate:"required"`
}
NodeSortRequest {
Sort []SortItem `json:"sort"`
}
CreateRuleGroupRequest {
Name string `json:"name" validate:"required"`
Icon string `json:"icon"`
Tags []string `json:"tags"`
Rules string `json:"rules"`
Enable bool `json:"enable"`
}
UpdateRuleGroupRequest {
Id int64 `json:"id" validate:"required"`
Icon string `json:"icon"`
Name string `json:"name" validate:"required"`
Tags []string `json:"tags"`
Rules string `json:"rules"`
Enable bool `json:"enable"`
}
DeleteRuleGroupRequest {
Id int64 `json:"id" validate:"required"`
}
GetRuleGroupResponse {
Total int64 `json:"total"`
List []ServerRuleGroup `json:"list"`
}
GetNodeTagListResponse {
Tags []string `json:"tags"`
}
)
@server (
prefix: v1/admin/server
group: admin/server
middleware: AuthMiddleware
)
service ppanel {
@doc "Get node tag list"
@handler GetNodeTagList
get /tag/list returns (GetNodeTagListResponse)
@doc "Get node list"
@handler GetNodeList
get /list (GetNodeServerListRequest) returns (GetNodeServerListResponse)
@doc "Get node detail"
@handler GetNodeDetail
get /detail (GetNodeDetailRequest) returns (Server)
@doc "Update node"
@handler UpdateNode
put / (UpdateNodeRequest)
@doc "Create node"
@handler CreateNode
post / (CreateNodeRequest)
@doc "Delete node"
@handler DeleteNode
delete / (DeleteNodeRequest)
@doc "Batch delete node"
@handler BatchDeleteNode
delete /batch (BatchDeleteNodeRequest)
@doc "Get node group list"
@handler GetNodeGroupList
get /group/list returns (GetNodeGroupListResponse)
@doc "Create node group"
@handler CreateNodeGroup
post /group (CreateNodeGroupRequest)
@doc "Update node group"
@handler UpdateNodeGroup
put /group (UpdateNodeGroupRequest)
@doc "Delete node group"
@handler DeleteNodeGroup
delete /group (DeleteNodeGroupRequest)
@doc "Batch delete node group"
@handler BatchDeleteNodeGroup
delete /group/batch (BatchDeleteNodeGroupRequest)
@doc "Node sort "
@handler NodeSort
post /sort (NodeSortRequest)
@doc "Create rule group"
@handler CreateRuleGroup
post /rule_group (CreateRuleGroupRequest)
@doc "Update rule group"
@handler UpdateRuleGroup
put /rule_group (UpdateRuleGroupRequest)
@doc "Delete rule group"
@handler DeleteRuleGroup
delete /rule_group (DeleteRuleGroupRequest)
@doc "Get rule group list"
@handler GetRuleGroupList
get /rule_group_list returns (GetRuleGroupResponse)
}

164
apis/admin/subscribe.api Normal file
View File

@ -0,0 +1,164 @@
syntax = "v1"
info (
title: "Subscribe API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
GetSubscribeDetailsRequest {
Id int64 `form:"id" validate:"required"`
}
CreateSubscribeGroupRequest {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
}
UpdateSubscribeGroupRequest {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Description string `json:"description"`
}
GetSubscribeGroupListResponse {
List []SubscribeGroup `json:"list"`
Total int64 `json:"total"`
}
DeleteSubscribeGroupRequest {
Id int64 `json:"id" validate:"required"`
}
BatchDeleteSubscribeGroupRequest {
Ids []int64 `json:"ids" validate:"required"`
}
CreateSubscribeRequest {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
UnitPrice int64 `json:"unit_price"`
UnitTime string `json:"unit_time"`
Discount []SubscribeDiscount `json:"discount"`
Replacement int64 `json:"replacement"`
Inventory int64 `json:"inventory"`
Traffic int64 `json:"traffic"`
SpeedLimit int64 `json:"speed_limit"`
DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"`
Server []int64 `json:"server"`
Show *bool `json:"show"`
Sell *bool `json:"sell"`
DeductionRatio int64 `json:"deduction_ratio"`
AllowDeduction *bool `json:"allow_deduction"`
ResetCycle int64 `json:"reset_cycle"`
RenewalReset *bool `json:"renewal_reset"`
}
UpdateSubscribeRequest {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Description string `json:"description"`
UnitPrice int64 `json:"unit_price"`
UnitTime string `json:"unit_time"`
Discount []SubscribeDiscount `json:"discount"`
Replacement int64 `json:"replacement"`
Inventory int64 `json:"inventory"`
Traffic int64 `json:"traffic"`
SpeedLimit int64 `json:"speed_limit"`
DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"`
Server []int64 `json:"server"`
Show *bool `json:"show"`
Sell *bool `json:"sell"`
Sort int64 `json:"sort"`
DeductionRatio int64 `json:"deduction_ratio"`
AllowDeduction *bool `json:"allow_deduction"`
ResetCycle int64 `json:"reset_cycle"`
RenewalReset *bool `json:"renewal_reset"`
}
SubscribeSortRequest {
Sort []SortItem `json:"sort"`
}
GetSubscribeListRequest {
Page int64 `form:"page" validate:"required"`
Size int64 `form:"size" validate:"required"`
GroupId int64 `form:"group_id,omitempty"`
Search string `form:"search,omitempty"`
}
SubscribeItem {
Subscribe
Sold int64 `json:"sold"`
}
GetSubscribeListResponse {
List []SubscribeItem `json:"list"`
Total int64 `json:"total"`
}
DeleteSubscribeRequest {
Id int64 `json:"id" validate:"required"`
}
BatchDeleteSubscribeRequest {
Ids []int64 `json:"ids" validate:"required"`
}
)
@server (
prefix: v1/admin/subscribe
group: admin/subscribe
middleware: AuthMiddleware
)
service ppanel {
@doc "Create subscribe group"
@handler CreateSubscribeGroup
post /group (CreateSubscribeGroupRequest)
@doc "Update subscribe group"
@handler UpdateSubscribeGroup
put /group (UpdateSubscribeGroupRequest)
@doc "Get subscribe group list"
@handler GetSubscribeGroupList
get /group/list returns (GetSubscribeGroupListResponse)
@doc "Delete subscribe group"
@handler DeleteSubscribeGroup
delete /group (DeleteSubscribeGroupRequest)
@doc "Batch delete subscribe group"
@handler BatchDeleteSubscribeGroup
delete /group/batch (BatchDeleteSubscribeGroupRequest)
@doc "Create subscribe"
@handler CreateSubscribe
post / (CreateSubscribeRequest)
@doc "Update subscribe"
@handler UpdateSubscribe
put / (UpdateSubscribeRequest)
@doc "Get subscribe list"
@handler GetSubscribeList
get /list (GetSubscribeListRequest) returns (GetSubscribeListResponse)
@doc "Delete subscribe"
@handler DeleteSubscribe
delete / (DeleteSubscribeRequest)
@doc "Batch delete subscribe"
@handler BatchDeleteSubscribe
delete /batch (BatchDeleteSubscribeRequest)
@doc "Get subscribe details"
@handler GetSubscribeDetails
get /details (GetSubscribeDetailsRequest) returns (Subscribe)
@doc "Subscribe sort"
@handler SubscribeSort
post /sort (SubscribeSortRequest)
}

205
apis/admin/system.api Normal file
View File

@ -0,0 +1,205 @@
syntax = "v1"
info (
title: "System API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
// Update application request
UpdateApplicationRequest {
Id int64 `json:"id" validate:"required"`
Icon string `json:"icon"`
Name string `json:"name"`
Description string `json:"description"`
SubscribeType string `json:"subscribe_type"`
Platform ApplicationPlatform `json:"platform"`
}
// Create application request
CreateApplicationRequest {
Icon string `json:"icon"`
Name string `json:"name"`
Description string `json:"description"`
SubscribeType string `json:"subscribe_type"`
Platform ApplicationPlatform `json:"platform"`
}
// Update application request
UpdateApplicationVersionRequest {
Id int64 `json:"id" validate:"required"`
Url string `json:"url"`
Version string `json:"version" validate:"required"`
Description string `json:"description"`
Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"`
IsDefault bool `json:"is_default"`
ApplicationId int64 `json:"application_id" validate:"required"`
}
// Create application request
CreateApplicationVersionRequest {
Url string `json:"url"`
Version string `json:"version" validate:"required"`
Description string `json:"description"`
Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"`
IsDefault bool `json:"is_default"`
ApplicationId int64 `json:"application_id" validate:"required"`
}
// Delete application request
DeleteApplicationRequest {
Id int64 `json:"id" validate:"required"`
}
// Delete application request
DeleteApplicationVersionRequest {
Id int64 `json:"id" validate:"required"`
}
GetNodeMultiplierResponse {
Periods []TimePeriod `json:"periods"`
}
// SetNodeMultiplierRequest
SetNodeMultiplierRequest {
Periods []TimePeriod `json:"periods"`
}
)
@server (
prefix: v1/admin/system
group: admin/system
middleware: AuthMiddleware
)
service ppanel {
@doc "Get site config"
@handler GetSiteConfig
get /site_config returns (SiteConfig)
@doc "Update site config"
@handler UpdateSiteConfig
put /site_config (SiteConfig)
@doc "Get subscribe config"
@handler GetSubscribeConfig
get /subscribe_config returns (SubscribeConfig)
@doc "Update subscribe config"
@handler UpdateSubscribeConfig
put /subscribe_config (SubscribeConfig)
@doc "Get subscribe type"
@handler GetSubscribeType
get /subscribe_type returns (SubscribeType)
@doc "update application config"
@handler UpdateApplicationConfig
put /application_config (ApplicationConfig)
@doc "get application config"
@handler GetApplicationConfig
get /application_config returns (ApplicationConfig)
@doc "Get application"
@handler GetApplication
get /application returns (ApplicationResponse)
@doc "Update application"
@handler UpdateApplication
put /application (UpdateApplicationRequest)
@doc "Create application"
@handler CreateApplication
post /application (CreateApplicationRequest)
@doc "Delete application"
@handler DeleteApplication
delete /application (DeleteApplicationRequest)
@doc "Update application version"
@handler UpdateApplicationVersion
put /application_version (UpdateApplicationVersionRequest)
@doc "Create application version"
@handler CreateApplicationVersion
post /application_version (CreateApplicationVersionRequest)
@doc "Delete application"
@handler DeleteApplicationVersion
delete /application_version (DeleteApplicationVersionRequest)
@doc "Get register config"
@handler GetRegisterConfig
get /register_config returns (RegisterConfig)
@doc "Update register config"
@handler UpdateRegisterConfig
put /register_config (RegisterConfig)
@doc "Get verify config"
@handler GetVerifyConfig
get /verify_config returns (VerifyConfig)
@doc "Update verify config"
@handler UpdateVerifyConfig
put /verify_config (VerifyConfig)
@doc "Get node config"
@handler GetNodeConfig
get /node_config returns (NodeConfig)
@doc "Update node config"
@handler UpdateNodeConfig
put /node_config (NodeConfig)
@doc "Get invite config"
@handler GetInviteConfig
get /invite_config returns (InviteConfig)
@doc "Update invite config"
@handler UpdateInviteConfig
put /invite_config (InviteConfig)
@doc "Get Team of Service Config"
@handler GetTosConfig
get /tos_config returns (TosConfig)
@doc "Update Team of Service Config"
@handler UpdateTosConfig
put /tos_config (TosConfig)
@doc "get Privacy Policy Config"
@handler GetPrivacyPolicyConfig
get /privacy returns (PrivacyPolicyConfig)
@doc "Update Privacy Policy Config"
@handler UpdatePrivacyPolicyConfig
put /privacy (PrivacyPolicyConfig)
@doc "Get Currency Config"
@handler GetCurrencyConfig
get /currency_config returns (CurrencyConfig)
@doc "Update Currency Config"
@handler UpdateCurrencyConfig
put /currency_config (CurrencyConfig)
@doc "setting telegram bot"
@handler SettingTelegramBot
post /setting_telegram_bot
@doc "Get Node Multiplier"
@handler GetNodeMultiplier
get /get_node_multiplier returns (GetNodeMultiplierResponse)
@doc "Set Node Multiplier"
@handler SetNodeMultiplier
post /set_node_multiplier (SetNodeMultiplierRequest)
@doc "Get Verify Code Config"
@handler GetVerifyCodeConfig
get /verify_code_config returns (VerifyCodeConfig)
@doc "Update Verify Code Config"
@handler UpdateVerifyCodeConfig
put /verify_code_config (VerifyCodeConfig)
}

62
apis/admin/ticket.api Normal file
View File

@ -0,0 +1,62 @@
syntax = "v1"
info (
title: "Ticket API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
UpdateTicketStatusRequest {
Id int64 `json:"id" validate:"required"`
Status *uint8 `json:"status" validate:"required"`
}
GetTicketListRequest {
Page int64 `form:"page"`
Size int64 `form:"size"`
UserId int64 `form:"user_id,omitempty"`
Status *uint8 `form:"status,omitempty"`
Search string `form:"search,omitempty"`
}
GetTicketListResponse {
Total int64 `json:"total"`
List []Ticket `json:"list"`
}
GetTicketRequest {
Id int64 `form:"id" validate:"required"`
}
CreateTicketFollowRequest {
TicketId int64 `json:"ticket_id" validate:"required"`
From string `json:"from" validate:"required"`
Type uint8 `json:"type" validate:"required"`
Content string `json:"content" validate:"required"`
}
)
@server (
prefix: v1/admin/ticket
group: admin/ticket
middleware: AuthMiddleware
)
service ppanel {
@doc "Get ticket list"
@handler GetTicketList
get /list (GetTicketListRequest) returns (GetTicketListResponse)
@doc "Get ticket detail"
@handler GetTicket
get /detail (GetTicketRequest) returns (Ticket)
@doc "Update ticket status"
@handler UpdateTicketStatus
put / (UpdateTicketStatusRequest)
@doc "Create ticket follow"
@handler CreateTicketFollow
post /follow (CreateTicketFollowRequest)
}

33
apis/admin/tool.api Normal file
View File

@ -0,0 +1,33 @@
syntax = "v1"
info(
title: "Tools Api"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
LogResponse {
List interface{} `json:"list"`
}
)
@server (
prefix: v1/admin/tool
group: admin/tool
middleware: AuthMiddleware
)
service ppanel {
@doc "Get System Log"
@handler GetSystemLog
get /log returns (LogResponse)
@doc "Restart System"
@handler RestartSystem
get /restart
}

278
apis/admin/user.api Normal file
View File

@ -0,0 +1,278 @@
syntax = "v1"
info (
title: "User API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"../types.api"
)
type (
// GetUserListRequest
GetUserListRequest {
Page int `form:"page"`
Size int `form:"size"`
Search string `form:"search,omitempty"`
UserId *int64 `form:"user_id,omitempty"`
SubscribeId *int64 `form:"subscribe_id,omitempty"`
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
}
// GetUserListResponse
GetUserListResponse {
Total int64 `json:"total"`
List []User `json:"list"`
}
// GetUserDetail
GetDetailRequest {
Id int64 `form:"id" validate:"required"`
}
UpdateUserBasiceInfoRequest {
UserId int64 `json:"user_id" validate:"required"`
Password string `json:"password"`
Avatar string `json:"avatar"`
Balance int64 `json:"balance"`
Commission int64 `json:"commission"`
GiftAmount int64 `json:"gift_amount"`
Telegram int64 `json:"telegram"`
ReferCode string `json:"refer_code"`
RefererId int64 `json:"referer_id"`
Enable bool `json:"enable"`
IsAdmin bool `json:"is_admin"`
}
UpdateUserNotifySettingRequest {
UserId int64 `json:"user_id" validate:"required"`
EnableBalanceNotify bool `json:"enable_balance_notify"`
EnableLoginNotify bool `json:"enable_login_notify"`
EnableSubscribeNotify bool `json:"enable_subscribe_notify"`
EnableTradeNotify bool `json:"enable_trade_notify"`
}
CreateUserRequest {
Email string `json:"email"`
Telephone string `json:"telephone"`
TelephoneAreaCode string `json:"telephone_area_code"`
Password string `json:"password"`
ProductId int64 `json:"product_id"`
Duration int64 `json:"duration"`
RefererUser string `json:"referer_user"`
ReferCode string `json:"refer_code"`
Balance int64 `json:"balance"`
Commission int64 `json:"commission"`
GiftAmount int64 `json:"gift_amount"`
IsAdmin bool `json:"is_admin"`
}
UserSubscribeDetail {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
User User `json:"user"`
OrderId int64 `json:"order_id"`
SubscribeId int64 `json:"subscribe_id"`
Subscribe Subscribe `json:"subscribe"`
StartTime int64 `json:"start_time"`
ExpireTime int64 `json:"expire_time"`
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"`
}
BatchDeleteUserRequest {
Ids []int64 `json:"ids" validate:"required"`
}
DeleteUserDeivceRequest {
Id int64 `json:"id"`
}
KickOfflineRequest {
Id int64 `json:"id"`
}
CreateUserAuthMethodRequest {
UserId int64 `json:"user_id"`
AuthType string `json:"auth_type"`
AuthIdentifier string `json:"auth_identifier"`
}
DeleteUserAuthMethodRequest {
UserId int64 `json:"user_id"`
AuthType string `json:"auth_type"`
}
UpdateUserAuthMethodRequest {
UserId int64 `json:"user_id"`
AuthType string `json:"auth_type"`
AuthIdentifier string `json:"auth_identifier"`
}
GetUserAuthMethodRequest {
UserId int64 `json:"user_id"`
}
GetUserAuthMethodResponse {
AuthMethods []UserAuthMethod `json:"auth_methods"`
}
GetUserSubscribeListRequest {
Page int `form:"page"`
Size int `form:"size"`
UserId int64 `form:"user_id"`
}
GetUserSubscribeListResponse {
List []UserSubscribe `json:"list"`
Total int64 `json:"total"`
}
GetUserSubscribeLogsRequest {
Page int `form:"page"`
Size int `form:"size"`
UserId int64 `form:"user_id"`
SubscribeId int64 `form:"subscribe_id,omitempty"`
}
GetUserSubscribeLogsResponse {
List []UserSubscribeLog `json:"list"`
Total int64 `json:"total"`
}
GetUserSubscribeDevicesRequest {
Page int `form:"page"`
Size int `form:"size"`
UserId int64 `form:"user_id"`
SubscribeId int64 `form:"subscribe_id"`
}
GetUserSubscribeDevicesResponse {
List []UserDevice `json:"list"`
Total int64 `json:"total"`
}
CreateUserSubscribeRequest {
UserId int64 `json:"user_id"`
ExpiredAt int64 `json:"expired_at"`
Traffic int64 `json:"traffic"`
SubscribeId int64 `json:"subscribe_id"`
}
UpdateUserSubscribeRequest {
UserSubscribeId int64 `json:"user_subscribe_id"`
SubscribeId int64 `json:"subscribe_id"`
Traffic int64 `json:"traffic"`
ExpiredAt int64 `json:"expired_at"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
}
GetUserLoginLogsRequest {
Page int `form:"page"`
Size int `form:"size"`
UserId int64 `form:"user_id"`
}
GetUserLoginLogsResponse {
List []UserLoginLog `json:"list"`
Total int64 `json:"total"`
}
DeleteUserSubscribeRequest {
UserSubscribeId int64 `json:"user_subscribe_id"`
}
GetUserSubscribeByIdRequest {
Id int64 `form:"id" validate:"required"`
}
)
@server (
prefix: v1/admin/user
group: admin/user
jwt: JwtAuth
middleware: AuthMiddleware
)
service ppanel {
@doc "Get user list"
@handler GetUserList
get /list (GetUserListRequest) returns (GetUserListResponse)
@doc "Get user detail"
@handler GetUserDetail
get /detail (GetDetailRequest) returns (User)
@doc "Update user basic info"
@handler UpdateUserBasicInfo
put /basic (UpdateUserBasiceInfoRequest)
@doc "Update user notify setting"
@handler UpdateUserNotifySetting
put /notify (UpdateUserNotifySettingRequest)
@doc "Delete user"
@handler DeleteUser
delete / (GetDetailRequest)
@doc "Current user"
@handler CurrentUser
get /current returns (User)
@doc "Batch delete user"
@handler BatchDeleteUser
delete /batch (BatchDeleteUserRequest)
@doc "Create user"
@handler CreateUser
post / (CreateUserRequest)
@doc "User device"
@handler UpdateUserDevice
put /device (UserDevice)
@doc "Delete user device"
@handler DeleteUserDevice
delete /device (DeleteUserDeivceRequest)
@doc "kick offline user device"
@handler KickOfflineByUserDevice
put /device/kick_offline (KickOfflineRequest)
@doc "Create user auth method"
@handler CreateUserAuthMethod
post /auth_method (CreateUserAuthMethodRequest)
@doc "Delete user auth method"
@handler DeleteUserAuthMethod
delete /auth_method (DeleteUserAuthMethodRequest)
@doc "Update user auth method"
@handler UpdateUserAuthMethod
put /auth_method (UpdateUserAuthMethodRequest)
@doc "Get user auth method"
@handler GetUserAuthMethod
get /auth_method (GetUserAuthMethodRequest) returns (GetUserAuthMethodResponse)
@doc "Get user subcribe"
@handler GetUserSubscribe
get /subscribe (GetUserSubscribeListRequest) returns (GetUserSubscribeListResponse)
@doc "Get user subcribe by id"
@handler GetUserSubscribeById
get /subscribe/detail (GetUserSubscribeByIdRequest) returns (UserSubscribeDetail)
@doc "Get user subcribe logs"
@handler GetUserSubscribeLogs
get /subscribe/logs (GetUserSubscribeLogsRequest) returns (GetUserSubscribeLogsResponse)
@doc "Get user subcribe traffic logs"
@handler GetUserSubscribeTrafficLogs
get /subscribe/traffic_logs (GetUserSubscribeTrafficLogsRequest) returns (GetUserSubscribeTrafficLogsResponse)
@doc "Get user subcribe devices"
@handler GetUserSubscribeDevices
get /subscribe/device (GetUserSubscribeDevicesRequest) returns (GetUserSubscribeDevicesResponse)
@doc "Create user subcribe"
@handler CreateUserSubscribe
post /subscribe (CreateUserSubscribeRequest)
@doc "Update user subcribe"
@handler UpdateUserSubscribe
put /subscribe (UpdateUserSubscribeRequest)
@doc "Delete user subcribe"
@handler DeleteUserSubscribe
delete /subscribe (DeleteUserSubscribeRequest)
@doc "Get user login logs"
@handler GetUserLoginLogs
get /login/logs (GetUserLoginLogsRequest) returns (GetUserLoginLogsResponse)
}

24
apis/app/announcement.api Normal file
View File

@ -0,0 +1,24 @@
syntax = "v1"
info (
title: "Announcement API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/app/announcement
group: app/announcement
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Query announcement"
@handler QueryAnnouncement
get /list (QueryAnnouncementRequest) returns (QueryAnnouncementResponse)
}

105
apis/app/auth.api Normal file
View File

@ -0,0 +1,105 @@
syntax = "v1"
info(
title: "App Auth Api"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"../types.api"
)
type (
AppAuthCheckRequest {
Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"`
Account string `json:"account"`
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"`
AreaCode string `json:"area_code"`
}
AppAuthCheckResponse {
Status bool
}
AppAuthRequest {
Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"`
Account string `json:"account"`
Password string `json:"password"`
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"`
Code string `json:"code"`
Invite string `json:"invite"`
AreaCode string `json:"area_code"`
CfToken string `json:"cf_token,optional"`
}
AppAuthRespone {
Token string `json:"token"`
}
AppSendCodeRequest {
Method string `json:"method" validate:"required" validate:"required,oneof=email mobile"`
Account string `json:"account"`
AreaCode string `json:"area_code"`
CfToken string `json:"cf_token,optional"`
}
AppSendCodeRespone {
Status bool `json:"status"`
Code string `json:"code,omitempty"`
}
AppConfigRequest {
UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"`
}
AppConfigResponse {
EncryptionKey string `json:"encryption_key"`
EncryptionMethod string `json:"encryption_method"`
Domains []string `json:"domains"`
StartupPicture string `json:"startup_picture"`
StartupPictureSkipTime int64 `json:"startup_picture_skip_time"`
Application AppInfo `json:"applications"`
OfficialEmail string `json:"official_email"`
OfficialWebsite string `json:"official_website"`
OfficialTelegram string `json:"official_telegram"`
OfficialTelephone string `json:"official_telephone"`
InvitationLink string `json:"invitation_link"`
KrWebsiteId string `json:"kr_website_id"`
}
AppInfo {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
Version string `json:"version"`
VersionReview string `json:"version_review"`
VersionDescription string `json:"version_description"`
IsDefault bool `json:"is_default"`
}
)
@server(
prefix: v1/app/auth
group: app/auth
middleware: AppMiddleware
)
service ppanel {
@doc "Check Account"
@handler Check
post /check (AppAuthCheckRequest) returns (AppAuthCheckResponse)
@doc "Login"
@handler Login
post /login (AppAuthRequest) returns (AppAuthRespone)
@doc "Register"
@handler Register
post /register (AppAuthRequest) returns (AppAuthRespone)
@doc "Reset Password"
@handler ResetPassword
post /reset_password (AppAuthRequest) returns (AppAuthRespone)
@doc "GetAppConfig"
@handler GetAppConfig
post /config (AppConfigRequest) returns (AppConfigResponse)
}

27
apis/app/document.api Normal file
View File

@ -0,0 +1,27 @@
syntax = "v1"
info(
title: "Document API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/app/document
group: app/document
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Get document list"
@handler QueryDocumentList
get /list returns (QueryDocumentListResponse)
@doc "Get document detail"
@handler QueryDocumentDetail
get /detail (QueryDocumentDetailRequest) returns (Document)
}

49
apis/app/node.api Normal file
View File

@ -0,0 +1,49 @@
syntax = "v1"
info(
title: "App Node Api"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type(
AppRuleGroupListResponse {
Total int64 `json:"total"`
List []ServerRuleGroup `json:"list"`
}
AppUserSubscbribeNodeRequest {
Id int64 `form:"id" validate:"required"`
}
AppUserSubscbribeNodeResponse{
List []AppUserSubscbribeNode `json:"list"`
}
)
@server (
prefix: v1/app/node
group: app/node
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Get Node list"
@handler GetNodeList
get /list (AppUserSubscbribeNodeRequest) returns(AppUserSubscbribeNodeResponse)
@doc "Get rule group list"
@handler GetRuleGroupList
get /rule_group_list returns (AppRuleGroupListResponse)
}

58
apis/app/order.api Normal file
View File

@ -0,0 +1,58 @@
syntax = "v1"
info (
title: "Order API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"../types.api"
)
@server (
prefix: v1/app/order
group: app/order
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Pre create order"
@handler PreCreateOrder
post /pre (PurchaseOrderRequest) returns (PreOrderResponse)
@doc "purchase Subscription"
@handler Purchase
post /purchase (PurchaseOrderRequest) returns (PurchaseOrderResponse)
@doc "Renewal Subscription"
@handler Renewal
post /renewal (RenewalOrderRequest) returns (RenewalOrderResponse)
@doc "Reset traffic"
@handler ResetTraffic
post /reset (ResetTrafficOrderRequest) returns (ResetTrafficOrderResponse)
@doc "Recharge"
@handler Recharge
post /recharge (RechargeOrderRequest) returns (RechargeOrderResponse)
@doc "Checkout order"
@handler CheckoutOrder
post /checkout (CheckoutOrderRequest) returns (CheckoutOrderResponse)
@doc "Close order"
@handler CloseOrder
post /close (CloseOrderRequest)
@doc "Get order"
@handler QueryOrderDetail
get /detail (QueryOrderDetailRequest) returns (OrderDetail)
@doc "Get order list"
@handler QueryOrderList
get /list (QueryOrderListRequest) returns (QueryOrderListResponse)
}

23
apis/app/payment.api Normal file
View File

@ -0,0 +1,23 @@
syntax = "v1"
info (
title: "payment API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/app/payment
group: app/payment
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Get available payment methods"
@handler GetAvailablePaymentMethods
get /methods returns (GetAvailablePaymentMethodsResponse)
}

75
apis/app/subscribe.api Normal file
View File

@ -0,0 +1,75 @@
syntax = "v1"
info(
title: "Subscribe API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
QueryUserSubscribeResp {
Data []UserSubscribeData `json:"data"`
}
UserSubscribeData {
SubscribeId int64 `json:"subscribe_id"`
UserSubscribeId int64 `json:"user_subscribe_id"`
}
UserSubscribeResetPeriodRequest {
UserSubscribeId int64 `json:"user_subscribe_id"`
}
UserSubscribeResetPeriodResponse {
Status bool `json:"status"`
}
AppUserSubscribeRequest {
ContainsNodes *bool `form:"contains_nodes"`
}
AppUserSubscbribeResponse {
List []AppUserSubcbribe `json:"list"`
}
)
@server(
prefix: v1/app/subscribe
group: app/subscribe
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "Get subscribe list"
@handler QuerySubscribeList
get /list returns (QuerySubscribeListResponse)
@doc "Get subscribe group list"
@handler QuerySubscribeGroupList
get /group/list returns (QuerySubscribeGroupListResponse)
@doc "Get application config"
@handler QueryApplicationConfig
get /application/config returns (ApplicationResponse)
@doc "Get Already subscribed to package"
@handler QueryUserAlreadySubscribe
get /user/already_subscribe returns (QueryUserSubscribeResp)
@doc "Get Available subscriptions for users"
@handler QueryUserAvailableUserSubscribe
get /user/available_subscribe (AppUserSubscribeRequest) returns (AppUserSubscbribeResponse)
@doc "Reset user subscription period"
@handler ResetUserSubscribePeriod
post /reset/period (UserSubscribeResetPeriodRequest) returns (UserSubscribeResetPeriodResponse)
}

90
apis/app/user.api Normal file
View File

@ -0,0 +1,90 @@
syntax = "v1"
info (
title: "App User Api"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"../types.api"
)
type (
UserInfoResponse {
Id int64 `json:"id"`
Balance int64 `json:"balance"`
Email string `json:"email"`
RefererId int64 `json:"referer_id"`
ReferCode string `json:"refer_code"`
Avatar string `json:"avatar"`
AreaCode string `json:"area_code"`
Telephone string `json:"telephone"`
Devices []UserDevice `json:"devices"`
AuthMethods []UserAuthMethod `json:"auth_methods"`
}
UpdatePasswordRequeset {
Password string `json:"password"`
NewPassword string `json:"new_password"`
}
DeleteAccountRequest {
Method string `json:"method" validate:"required" validate:"required,oneof=email telephone device"`
Code string `json:"code"`
}
GetUserOnlineTimeStatisticsResponse{
WeeklyStats []WeeklyStat`json:"weekly_stats"`
ConnectionRecords ConnectionRecords`json:"connection_records"`
}
WeeklyStat{
Day int `json:"day"`
DayName string `json:"day_name"`
Hours float64 `json:"hours"`
}
ConnectionRecords{
CurrentContinuousDays int64 `json:"current_continuous_days"`
HistoryContinuousDays int64 `json:"history_continuous_days"`
LongestSingleConnection int64 `json:"longest_single_connection"`
}
)
@server (
prefix: v1/app/user
group: app/user
middleware: AppMiddleware,AuthMiddleware
)
service ppanel {
@doc "query user info"
@handler QueryUserInfo
get /info returns (UserInfoResponse)
@doc "Update Password "
@handler UpdatePassword
put /password (UpdatePasswordRequeset)
@doc "Delete Account"
@handler DeleteAccount
delete /account (DeleteAccountRequest)
@doc "Get user subcribe traffic logs"
@handler GetUserSubscribeTrafficLogs
get /subscribe/traffic_logs (GetUserSubscribeTrafficLogsRequest) returns (GetUserSubscribeTrafficLogsResponse)
@doc "Get user online time total"
@handler GetUserOnlineTimeStatistics
get /online_time/statistics returns (GetUserOnlineTimeStatisticsResponse)
@doc "Query User Affiliate List"
@handler QueryUserAffiliateList
get /affiliate/list (QueryUserAffiliateListRequest) returns (QueryUserAffiliateListResponse)
@doc "Query User Affiliate Count"
@handler QueryUserAffiliate
get /affiliate/count returns (QueryUserAffiliateCountResponse)
}

23
apis/app/ws.api Normal file
View File

@ -0,0 +1,23 @@
syntax = "v1"
info(
title: "App Heartbeat Api"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
@server(
prefix: v1/app/ws
group: app/ws
middleware: AuthMiddleware
)
service ppanel {
@doc "App heartbeat"
@handler AppWs
get /:userid/:identifier
}

166
apis/auth/auth.api Normal file
View File

@ -0,0 +1,166 @@
syntax = "v1"
info (
title: "User auth API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
type (
// User login request
UserLoginRequest {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"`
CfToken string `json:"cf_token,optional"`
}
// Check user is exist request
CheckUserRequest {
Email string `form:"email" validate:"required"`
}
// User login response
CheckUserResponse {
exist bool `json:"exist"`
}
// User login response
UserRegisterRequest {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
Invite string `json:"invite,optional"`
Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"`
CfToken string `json:"cf_token,optional"`
}
// User login response
ResetPasswordRequest {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"`
UserAgent string `header:"User-Agent"`
CfToken string `json:"cf_token,optional"`
}
LoginResponse {
Token string `json:"token"`
}
OAthLoginRequest {
Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc.
Redirect string `json:"redirect"`
}
OAuthLoginResponse {
Redirect string `json:"redirect"`
}
OAuthLoginGetTokenRequest {
Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc.
Callback interface{} `json:"callback" validate:"required"`
}
// login request
TelephoneLoginRequest {
Telephone string `json:"telephone" validate:"required"`
TelephoneCode string `json:"telephone_code"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password"`
IP string `header:"X-Original-Forwarded-For"`
CfToken string `json:"cf_token,optional"`
}
// Check user is exist request
TelephoneCheckUserRequest {
Telephone string `form:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
}
// User login response
TelephoneCheckUserResponse {
exist bool `json:"exist"`
}
// User login response
TelephoneRegisterRequest {
Telephone string `json:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password" validate:"required"`
Invite string `json:"invite,optional"`
Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"`
CfToken string `json:"cf_token,optional"`
}
// User login response
TelephoneResetPasswordRequest {
Telephone string `json:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
Password string `json:"password" validate:"required"`
Code string `json:"code,optional"`
IP string `header:"X-Original-Forwarded-For"`
CfToken string `json:"cf_token,optional"`
}
AppleLoginCallbackRequest {
Code string `form:"code"`
IDToken string `form:"id_token"`
State string `form:"state"`
}
GoogleLoginCallbackRequest {
Code string `form:"code"`
State string `form:"state"`
}
)
@server (
prefix: v1/auth
group: auth
)
service ppanel {
@doc "User login"
@handler UserLogin
post /login (UserLoginRequest) returns (LoginResponse)
@doc "Check user is exist"
@handler CheckUser
get /check (CheckUserRequest) returns (CheckUserResponse)
@doc "User register"
@handler UserRegister
post /register (UserRegisterRequest) returns (LoginResponse)
@doc "Reset password"
@handler ResetPassword
post /reset (ResetPasswordRequest) returns (LoginResponse)
@doc "User Telephone login"
@handler TelephoneLogin
post /login/telephone (TelephoneLoginRequest) returns (LoginResponse)
@doc "Check user telephone is exist"
@handler CheckUserTelephone
get /check/telephone (TelephoneCheckUserRequest) returns (TelephoneCheckUserResponse)
@doc "User Telephone register"
@handler TelephoneUserRegister
post /register/telephone (TelephoneRegisterRequest) returns (LoginResponse)
@doc "Reset password"
@handler TelephoneResetPassword
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
}
@server (
prefix: v1/auth/oauth
group: auth/oauth
)
service ppanel {
@doc "OAuth login"
@handler OAuthLogin
post /login (OAthLoginRequest) returns (OAuthLoginResponse)
@doc "OAuth login get token"
@handler OAuthLoginGetToken
post /login/token (OAuthLoginGetTokenRequest) returns (LoginResponse)
@doc "Apple Login Callback"
@handler AppleLoginCallback
post /callback/apple (AppleLoginCallbackRequest)
}

125
apis/common.api Normal file
View File

@ -0,0 +1,125 @@
syntax = "v1"
info (
title: "Common API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "./types.api"
type (
VeifyConfig {
TurnstileSiteKey string `json:"turnstile_site_key"`
EnableLoginVerify bool `json:"enable_login_verify"`
EnableRegisterVerify bool `json:"enable_register_verify"`
EnableResetPasswordVerify bool `json:"enable_reset_password_verify"`
}
GetGlobalConfigResponse {
Site SiteConfig `json:"site"`
Verify VeifyConfig `json:"verify"`
Auth AuthConfig `json:"auth"`
Invite InviteConfig `json:"invite"`
Currency Currency `json:"currency"`
Subscribe SubscribeConfig `json:"subscribe"`
VerifyCode PubilcVerifyCodeConfig `json:"verify_code"`
OAuthMethods []string `json:"oauth_methods"`
WebAd bool `json:"web_ad"`
}
Currency {
CurrencyUnit string `json:"currency_unit"`
CurrencySymbol string `json:"currency_symbol"`
}
GetTosResponse {
TosContent string `json:"tos_content"`
}
GetAppcationResponse {
Config ApplicationConfig `json:"config"`
Applications []ApplicationResponseInfo `json:"applications"`
}
// GetCodeRequest Get code request
SendCodeRequest {
Email string `json:"email" validate:"required"`
Type uint8 `json:"type" validate:"required"`
}
SendSmsCodeRequest {
Type uint8 `json:"type" validate:"required"`
Telephone string `json:"telephone" validate:"required"`
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
}
// GetCodeResponse Get code response
SendCodeResponse {
Code string `json:"code,omitempty"`
Status bool `json:"status"`
}
// GetStatResponse Get stat response
GetStatResponse {
User int64 `json:"user"`
Node int64 `json:"node"`
Country int64 `json:"country"`
Protocol []string `json:"protocol"`
}
// Get ads
GetAdsRequest {
Device string `form:"device"`
Position string `form:"position"`
}
GetAdsResponse {
List []Ads `json:"list"`
}
CheckVerificationCodeRequest {
Method string `json:"method" validate:"required,oneof=email mobile"`
Account string `json:"account" validate:"required"`
Code string `json:"code" validate:"required"`
Type uint8 `json:"type" validate:"required"`
}
CheckVerificationCodeRespone{
Status bool `json:"status"`
}
)
@server (
prefix: v1/common
group: common
)
service ppanel {
@doc "Get global config"
@handler GetGlobalConfig
get /site/config returns (GetGlobalConfigResponse)
@doc "Get Tos Content"
@handler GetApplication
get /application returns (GetAppcationResponse)
@doc "Get Tos Content"
@handler GetTos
get /site/tos returns (GetTosResponse)
@doc "Get Privacy Policy"
@handler GetPrivacyPolicy
get /site/privacy returns (PrivacyPolicyConfig)
@doc "Get stat"
@handler GetStat
get /site/stat returns (GetStatResponse)
@doc "Get verification code"
@handler SendEmailCode
post /send_code (SendCodeRequest) returns (SendCodeResponse)
@doc "Get sms verification code"
@handler SendSmsCode
post /send_sms_code (SendSmsCodeRequest) returns (SendCodeResponse)
@doc "Get Ads"
@handler GetAds
get /ads (GetAdsRequest) returns (GetAdsResponse)
@doc "Check verification code"
@handler CheckVerificationCode
post /check_verification_code (CheckVerificationCodeRequest) returns (CheckVerificationCodeRespone)
}

121
apis/node/node.api Normal file
View File

@ -0,0 +1,121 @@
syntax = "v1"
info (
title: "Node API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
ShadowsocksProtocol {
Port int `json:"port"`
Method string `json:"method"`
}
VmessProtocol {
Host string `json:"host"`
Port int `json:"port"`
EnableTLS *bool `json:"enable_tls"`
TLSConfig string `json:"tls_config"`
Network string `json:"network"`
Transport string `json:"transport"`
}
VlessProtocol {
Host string `json:"host"`
Port int `json:"port"`
Network string `json:"network"`
Transport string `json:"transport"`
Security string `json:"security"`
SecurityConfig string `json:"security_config"`
XTLS string `json:"xtls"`
}
TrojanProtocol {
Host string `json:"host"`
Port int `json:"port"`
EnableTLS *bool `json:"enable_tls"`
TLSConfig string `json:"tls_config"`
Network string `json:"network"`
Transport string `json:"transport"`
}
GetServerConfigRequest {
ServerCommon
}
GetServerConfigResponse {
Basic ServerBasic `json:"basic"`
Protocol string `json:"protocol"`
Config interface{} `json:"config"`
}
ServerBasic {
PushInterval int64 `json:"push_interval"`
PullInterval int64 `json:"pull_interval"`
}
ServerCommon {
Protocol string `form:"protocol"`
ServerId int64 `form:"server_id"`
SecretKey string `form:"secret_key"`
}
ServerUser {
Id int64 `json:"id"`
UUID string `json:"uuid"`
SpeedLimit int64 `json:"speed_limit"`
DeviceLimit int64 `json:"device_limit"`
}
GetServerUserListRequest {
ServerCommon
}
GetServerUserListResponse {
Users []ServerUser `json:"users"`
}
UserTraffic {
SID int64 `json:"uid"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
}
ServerPushUserTrafficRequest {
ServerCommon
Traffic []UserTraffic `json:"traffic"`
}
ServerPushStatusRequest {
ServerCommon
Cpu float64 `json:"cpu"`
Mem float64 `json:"mem"`
Disk float64 `json:"disk"`
UpdatedAt int64 `json:"updated_at"`
}
OnlineUsersRequest {
ServerCommon
Users []OnlineUser `json:"users"`
}
)
@server (
prefix: v1/server
group: server
middleware: ServerMiddleware
)
service ppanel {
@doc "Get user list"
@handler GetServerUserList
get /user (GetServerUserListRequest) returns (GetServerUserListResponse)
@doc "Push user Traffic"
@handler ServerPushUserTraffic
post /push (ServerPushUserTrafficRequest)
@doc "Push server status"
@handler ServerPushStatus
post /status (ServerPushStatusRequest)
@doc "Get server config"
@handler GetServerConfig
get /config (GetServerConfigRequest) returns (GetServerConfigResponse)
@doc "Push online users"
@handler PushOnlineUsers
post /online (OnlineUsersRequest)
}

View File

@ -0,0 +1,24 @@
syntax = "v1"
info (
title: "Announcement API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/public/announcement
group: public/announcement
middleware: AuthMiddleware
)
service ppanel {
@doc "Query announcement"
@handler QueryAnnouncement
get /list (QueryAnnouncementRequest) returns (QueryAnnouncementResponse)
}

28
apis/public/document.api Normal file
View File

@ -0,0 +1,28 @@
syntax = "v1"
info(
title: "Document API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/public/document
group: public/document
middleware: AuthMiddleware
)
service ppanel {
@doc "Get document list"
@handler QueryDocumentList
get /list returns (QueryDocumentListResponse)
@doc "Get document detail"
@handler QueryDocumentDetail
get /detail (QueryDocumentDetailRequest) returns (Document)
}

51
apis/public/order.api Normal file
View File

@ -0,0 +1,51 @@
syntax = "v1"
info (
title: "Order API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/public/order
group: public/order
middleware: AuthMiddleware
)
service ppanel {
@doc "Pre create order"
@handler PreCreateOrder
post /pre (PurchaseOrderRequest) returns (PreOrderResponse)
@doc "purchase Subscription"
@handler Purchase
post /purchase (PurchaseOrderRequest) returns (PurchaseOrderResponse)
@doc "Renewal Subscription"
@handler Renewal
post /renewal (RenewalOrderRequest) returns (RenewalOrderResponse)
@doc "Reset traffic"
@handler ResetTraffic
post /reset (ResetTrafficOrderRequest) returns (ResetTrafficOrderResponse)
@doc "Recharge"
@handler Recharge
post /recharge (RechargeOrderRequest) returns (RechargeOrderResponse)
@doc "Close order"
@handler CloseOrder
post /close (CloseOrderRequest)
@doc "Get order"
@handler QueryOrderDetail
get /detail (QueryOrderDetailRequest) returns (OrderDetail)
@doc "Get order list"
@handler QueryOrderList
get /list (QueryOrderListRequest) returns (QueryOrderListResponse)
}

24
apis/public/payment.api Normal file
View File

@ -0,0 +1,24 @@
syntax = "v1"
info (
title: "payment API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/public/payment
group: public/payment
middleware: AuthMiddleware
)
service ppanel {
@doc "Get available payment methods"
@handler GetAvailablePaymentMethods
get /methods returns (GetAvailablePaymentMethodsResponse)
}

95
apis/public/portal.api Normal file
View File

@ -0,0 +1,95 @@
syntax = "v1"
info (
title: "Portal API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
PortalPurchaseRequest {
AuthType string `json:"auth_type"`
Identifier string `json:"identifier"`
Password string `json:"password,omitempty"`
Payment int64 `json:"payment"`
SubscribeId int64 `json:"subscribe_id"`
Quantity int64 `json:"quantity"`
Coupon string `json:"coupon,omitempty"`
TurnstileToken string `json:"turnstile_token,omitempty"`
}
PortalPurchaseResponse {
OrderNo string `json:"order_no"`
}
GetSubscriptionResponse {
List []Subscribe `json:"list"`
}
PrePurchaseOrderRequest {
Payment int64 `json:"payment,omitempty"`
SubscribeId int64 `json:"subscribe_id"`
Quantity int64 `json:"quantity"`
Coupon string `json:"coupon,omitempty"`
}
PrePurchaseOrderResponse {
Price int64 `json:"price"`
Amount int64 `json:"amount"`
Discount int64 `json:"discount"`
Coupon string `json:"coupon"`
CouponDiscount int64 `json:"coupon_discount"`
FeeAmount int64 `json:"fee_amount"`
}
QueryPurchaseOrderRequest {
AuthType string `form:"auth_type"`
Identifier string `form:"identifier"`
OrderNo string `form:"order_no"`
}
QueryPurchaseOrderResponse {
OrderNo string `json:"order_no"`
Subscribe Subscribe `json:"subscribe"`
Quantity int64 `json:"quantity"`
Price int64 `json:"price"`
Amount int64 `json:"amount"`
Discount int64 `json:"discount"`
Coupon string `json:"coupon"`
CouponDiscount int64 `json:"coupon_discount"`
FeeAmount int64 `json:"fee_amount"`
Payment PaymentMethod `json:"payment"`
Status uint8 `json:"status"`
CreatedAt int64 `json:"created_at"`
Token string `json:"token,omitempty"`
}
)
@server (
prefix: v1/public/portal
group: public/portal
)
service ppanel {
@doc "Get available payment methods"
@handler GetAvailablePaymentMethods
get /payment-method returns (GetAvailablePaymentMethodsResponse)
@doc "Get Subscription"
@handler GetSubscription
get /subscribe returns (GetSubscriptionResponse)
@doc "Pre Purchase Order"
@handler PrePurchaseOrder
post /pre (PrePurchaseOrderRequest) returns (PrePurchaseOrderResponse)
@doc "Purchase subscription"
@handler Purchase
post /purchase (PortalPurchaseRequest) returns (PortalPurchaseResponse)
@doc "Query Purchase Order"
@handler QueryPurchaseOrder
get /order/status (QueryPurchaseOrderRequest) returns (QueryPurchaseOrderResponse)
@doc "Purchase Checkout"
@handler PurchaseCheckout
post /order/checkout (CheckoutOrderRequest) returns (CheckoutOrderResponse)
}

31
apis/public/subscribe.api Normal file
View File

@ -0,0 +1,31 @@
syntax = "v1"
info (
title: "Subscribe API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
@server (
prefix: v1/public/subscribe
group: public/subscribe
middleware: AuthMiddleware
)
service ppanel {
@doc "Get subscribe list"
@handler QuerySubscribeList
get /list returns (QuerySubscribeListResponse)
@doc "Get subscribe group list"
@handler QuerySubscribeGroupList
get /group/list returns (QuerySubscribeGroupListResponse)
@doc "Get application config"
@handler QueryApplicationConfig
get /application/config returns (ApplicationResponse)
}

70
apis/public/ticket.api Normal file
View File

@ -0,0 +1,70 @@
syntax = "v1"
info (
title: "Ticket API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
GetUserTicketListResponse {
Total int64 `json:"total"`
List []Ticket `json:"list"`
}
CreateUserTicketRequest {
Title string `json:"title"`
Description string `json:"description"`
}
GetUserTicketListRequest {
Page int `form:"page"`
Size int `form:"size"`
Status *uint8 `form:"status,omitempty"`
Search string `form:"search,omitempty"`
}
GetUserTicketDetailRequest {
Id int64 `form:"id" validate:"required"`
}
UpdateUserTicketStatusRequest {
Id int64 `json:"id" validate:"required"`
Status *uint8 `json:"status" validate:"required"`
}
CreateUserTicketFollowRequest {
TicketId int64 `json:"ticket_id"`
From string `json:"from"`
Type uint8 `json:"type"`
Content string `json:"content"`
}
)
@server (
prefix: v1/public/ticket
group: public/ticket
middleware: AuthMiddleware
)
service ppanel {
@doc "Get ticket list"
@handler GetUserTicketList
get /list (GetUserTicketListRequest) returns (GetUserTicketListResponse)
@doc "Get ticket detail"
@handler GetUserTicketDetails
get /detail (GetUserTicketDetailRequest) returns (Ticket)
@doc "Update ticket status"
@handler UpdateUserTicketStatus
put / (UpdateUserTicketStatusRequest)
@doc "Create ticket follow"
@handler CreateUserTicketFollow
post /follow (CreateUserTicketFollowRequest)
@doc "Create ticket"
@handler CreateUserTicket
post / (CreateUserTicketRequest)
}

211
apis/public/user.api Normal file
View File

@ -0,0 +1,211 @@
syntax = "v1"
info (
title: "User API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "../types.api"
type (
UpdateUserNotifyRequest {
EnableBalanceNotify *bool `json:"enable_balance_notify"`
EnableLoginNotify *bool `json:"enable_login_notify"`
EnableSubscribeNotify *bool `json:"enable_subscribe_notify"`
EnableTradeNotify *bool `json:"enable_trade_notify"`
}
UpdateUserPasswordRequest {
Password string `json:"password" validate:"required"`
}
QueryUserSubscribeListResponse {
List []UserSubscribe `json:"list"`
Total int64 `json:"total"`
}
QueryUserBalanceLogListResponse {
List []UserBalanceLog `json:"list"`
Total int64 `json:"total"`
}
CommissionLog {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
OrderNo string `json:"order_no"`
Amount int64 `json:"amount"`
CreatedAt int64 `json:"created_at"`
}
QueryUserCommissionLogListRequest {
Page int `form:"page"`
Size int `form:"size"`
}
QueryUserCommissionLogListResponse {
List []CommissionLog `json:"list"`
Total int64 `json:"total"`
}
BindTelegramResponse {
Url string `json:"url"`
ExpiredAt int64 `json:"expired_at"`
}
PreUnsubscribeRequest {
Id int64 `json:"id"`
}
PreUnsubscribeResponse {
DeductionAmount int64 `json:"deduction_amount"`
}
UnsubscribeRequest {
Id int64 `json:"id"`
}
BindOAuthRequest {
Method string `json:"method"`
Redirect string `json:"redirect"`
}
BindOAuthResponse {
Redirect string `json:"redirect"`
}
BindOAuthCallbackRequest {
Method string `json:"method"`
Callback interface{} `json:"callback"`
}
GetOAuthMethodsResponse {
Methods []UserAuthMethod `json:"methods"`
}
UnbindOAuthRequest {
Method string `json:"method"`
}
ResetUserSubscribeTokenRequest {
UserSubscribeId int64 `json:"user_subscribe_id"`
}
GetLoginLogRequest {
Page int `form:"page"`
Size int `form:"size"`
}
GetLoginLogResponse {
List []UserLoginLog `json:"list"`
Total int64 `json:"total"`
}
GetSubscribeLogRequest {
Page int `form:"page"`
Size int `form:"size"`
}
GetSubscribeLogResponse {
List []UserSubscribeLog `json:"list"`
Total int64 `json:"total"`
}
UpdateBindMobileRequest{
AreaCode string `json:"area_code" validate:"required"`
Mobile string `json:"mobile" validate:"required"`
Code string `json:"code" validate:"required"`
}
UpdateBindEmailRequest{
Email string `json:"email" validate:"required"`
}
VerifyEmailRequest {
Email string `json:"email" validate:"required"`
Code string `json:"code" validate:"required"`
}
)
@server (
prefix: v1/public/user
group: public/user
middleware: AuthMiddleware
)
service ppanel {
@doc "Query User Info"
@handler QueryUserInfo
get /info returns (User)
@doc "Update User Notify"
@handler UpdateUserNotify
put /notify (UpdateUserNotifyRequest)
@doc "Update User Password"
@handler UpdateUserPassword
put /password (UpdateUserPasswordRequest)
@doc "Query User Subscribe"
@handler QueryUserSubscribe
get /subscribe returns (QueryUserSubscribeListResponse)
@doc "Pre Unsubscribe"
@handler PreUnsubscribe
post /unsubscribe/pre (PreUnsubscribeRequest) returns (PreUnsubscribeResponse)
@doc "Unsubscribe"
@handler Unsubscribe
post /unsubscribe (UnsubscribeRequest)
@doc "Query User Balance Log"
@handler QueryUserBalanceLog
get /balance_log returns (QueryUserBalanceLogListResponse)
@doc "Query User Affiliate Count"
@handler QueryUserAffiliate
get /affiliate/count returns (QueryUserAffiliateCountResponse)
@doc "Query User Affiliate List"
@handler QueryUserAffiliateList
get /affiliate/list (QueryUserAffiliateListRequest) returns (QueryUserAffiliateListResponse)
@doc "Bind Telegram"
@handler BindTelegram
get /bind_telegram returns (BindTelegramResponse)
@doc "Unbind Telegram"
@handler UnbindTelegram
post /unbind_telegram
@doc "Query User Commission Log"
@handler QueryUserCommissionLog
get /commission_log (QueryUserCommissionLogListRequest) returns (QueryUserCommissionLogListResponse)
@doc "Bind OAuth"
@handler BindOAuth
post /bind_oauth (BindOAuthRequest) returns (BindOAuthResponse)
@doc "Bind OAuth Callback"
@handler BindOAuthCallback
post /bind_oauth/callback (BindOAuthCallbackRequest)
@doc "Get OAuth Methods"
@handler GetOAuthMethods
get /oauth_methods returns (GetOAuthMethodsResponse)
@doc "Unbind OAuth"
@handler UnbindOAuth
post /unbind_oauth (UnbindOAuthRequest)
@doc "Reset User Subscribe Token"
@handler ResetUserSubscribeToken
put /subscribe_token (ResetUserSubscribeTokenRequest)
@doc "Get Login Log"
@handler GetLoginLog
get /login_log (GetLoginLogRequest) returns (GetLoginLogResponse)
@doc "Get Subscribe Log"
@handler GetSubscribeLog
get /subscribe_log (GetSubscribeLogRequest) returns (GetSubscribeLogResponse)
@doc "Verify Email"
@handler VerifyEmail
post /verify_email (VerifyEmailRequest)
@doc "Update Bind Mobile"
@handler UpdateBindMobile
put /bind_mobile (UpdateBindMobileRequest)
@doc "Update Bind Email"
@handler UpdateBindEmail
put /bind_email (UpdateBindEmailRequest)
}

26
apis/swagger_admin.api Normal file
View File

@ -0,0 +1,26 @@
syntax = "v1"
info(
title: "admin API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"./admin/system.api"
"./admin/user.api"
"./admin/server.api"
"./admin/subscribe.api"
"./admin/payment.api"
"./admin/coupon.api"
"./admin/order.api"
"./admin/ticket.api"
"./admin/announcement.api"
"./admin/document.api"
"./admin/tool.api"
"./admin/console.api"
"./admin/auth.api"
"./admin/log.api"
"./admin/ads.api"
)

21
apis/swagger_app.api Normal file
View File

@ -0,0 +1,21 @@
syntax = "v1"
info(
title: "App API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"./app/auth.api"
"./app/user.api"
"./app/node.api"
"./app/ws.api"
"./app/order.api"
"./app/announcement.api"
"./app/payment.api"
"./app/document.api"
"./app/subscribe.api"
)

14
apis/swagger_common.api Normal file
View File

@ -0,0 +1,14 @@
syntax = "v1"
info(
title: "common API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"./common.api"
"./auth/auth.api"
)

11
apis/swagger_node.api Normal file
View File

@ -0,0 +1,11 @@
syntax = "v1"
info(
title: "Node API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import "./node/node.api"

19
apis/swagger_user.api Normal file
View File

@ -0,0 +1,19 @@
syntax = "v1"
info(
title: "User API"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
import (
"./public/user.api"
"./public/subscribe.api"
"./public/order.api"
"./public/announcement.api"
"./public/ticket.api"
"./public/payment.api"
"./public/document.api"
"./public/portal.api"
)

752
apis/types.api Normal file
View File

@ -0,0 +1,752 @@
syntax = "v1"
info (
title: "Interface Model"
desc: "API for ppanel"
author: "Tension"
email: "tension@ppanel.com"
version: "0.0.1"
)
type (
User {
Id int64 `json:"id"`
Avatar string `json:"avatar"`
Balance int64 `json:"balance"`
Commission int64 `json:"commission"`
GiftAmount int64 `json:"gift_amount"`
Telegram int64 `json:"telegram"`
ReferCode string `json:"refer_code"`
RefererId int64 `json:"referer_id"`
Enable bool `json:"enable"`
IsAdmin bool `json:"is_admin,omitempty"`
EnableBalanceNotify bool `json:"enable_balance_notify"`
EnableLoginNotify bool `json:"enable_login_notify"`
EnableSubscribeNotify bool `json:"enable_subscribe_notify"`
EnableTradeNotify bool `json:"enable_trade_notify"`
AuthMethods []UserAuthMethod `json:"auth_methods"`
UserDevices []UserDevice `json:"user_devices"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
DeletedAt int64 `json:"deleted_at,omitempty"`
IsDel bool `json:"is_del,omitempty"`
}
Follow {
Id int64 `json:"id"`
TicketId int64 `json:"ticket_id"`
From string `json:"from"`
Type uint8 `json:"type"`
Content string `json:"content"`
CreatedAt int64 `json:"created_at"`
}
Ticket {
Id int64 `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
UserId int64 `json:"user_id"`
Follows []Follow `json:"follow,omitempty"`
Status uint8 `json:"status"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
SiteConfig {
Host string `json:"host"`
SiteName string `json:"site_name"`
SiteDesc string `json:"site_desc"`
SiteLogo string `json:"site_logo"`
Keywords string `json:"keywords"`
CustomHTML string `json:"custom_html"`
CustomData string `json:"custom_data"`
}
SubscribeConfig {
SingleModel bool `json:"single_model"`
SubscribePath string `json:"subscribe_path"`
SubscribeDomain string `json:"subscribe_domain"`
PanDomain bool `json:"pan_domain"`
}
VerifyCodeConfig {
VerifyCodeExpireTime int64 `json:"verify_code_expire_time"`
VerifyCodeLimit int64 `json:"verify_code_limit"`
VerifyCodeInterval int64 `json:"verify_code_interval"`
}
PubilcVerifyCodeConfig {
VerifyCodeInterval int64 `json:"verify_code_interval"`
}
SubscribeType {
SubscribeTypes []string `json:"subscribe_types"`
}
Application {
Id int64 `json:"id"`
Icon string `json:"icon"`
Name string `json:"name"`
Description string `json:"description"`
SubscribeType string `json:"subscribe_type"`
}
ApplicationVersion {
Id int64 `json:"id"`
Url string `json:"url"`
Version string `json:"version" validate:"required"`
Description string `json:"description"`
IsDefault bool `json:"is_default"`
}
ApplicationResponse {
Applications []ApplicationResponseInfo `json:"applications"`
}
ApplicationResponseInfo {
Id int64 `json:"id"`
Name string `json:"name"`
Icon string `json:"icon"`
Description string `json:"description"`
SubscribeType string `json:"subscribe_type"`
Platform ApplicationPlatform `json:"platform"`
}
ApplicationPlatform {
IOS []*ApplicationVersion `json:"ios,omitempty"`
MacOS []*ApplicationVersion `json:"macos,omitempty"`
Linux []*ApplicationVersion `json:"linux,omitempty"`
Android []*ApplicationVersion `json:"android,omitempty"`
Windows []*ApplicationVersion `json:"windows,omitempty"`
Harmony []*ApplicationVersion `json:"harmony,omitempty"`
}
AuthConfig {
Mobile MobileAuthenticateConfig `json:"mobile"`
Email EmailAuthticateConfig `json:"email"`
Register PubilcRegisterConfig `json:"register"`
}
PubilcRegisterConfig {
StopRegister bool `json:"stop_register"`
EnableIpRegisterLimit bool `json:"enable_ip_register_limit"`
IpRegisterLimit int64 `json:"ip_register_limit"`
IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"`
}
MobileAuthenticateConfig {
Enable bool `json:"enable"`
EnableWhitelist bool `json:"enable_whitelist"`
Whitelist []string `json:"whitelist"`
}
EmailAuthticateConfig {
Enable bool `json:"enable"`
EnableVerify bool `json:"enable_verify"`
EnableDomainSuffix bool `json:"enable_domain_suffix"`
DomainSuffixList string `json:"domain_suffix_list"`
}
RegisterConfig {
StopRegister bool `json:"stop_register"`
EnableTrial bool `json:"enable_trial"`
TrialSubscribe int64 `json:"trial_subscribe"`
TrialTime int64 `json:"trial_time"`
TrialTimeUnit string `json:"trial_time_unit"`
EnableIpRegisterLimit bool `json:"enable_ip_register_limit"`
IpRegisterLimit int64 `json:"ip_register_limit"`
IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"`
}
VerifyConfig {
TurnstileSiteKey string `json:"turnstile_site_key"`
TurnstileSecret string `json:"turnstile_secret"`
EnableLoginVerify bool `json:"enable_login_verify"`
EnableRegisterVerify bool `json:"enable_register_verify"`
EnableResetPasswordVerify bool `json:"enable_reset_password_verify"`
}
NodeConfig {
NodeSecret string `json:"node_secret"`
NodePullInterval int64 `json:"node_pull_interval"`
NodePushInterval int64 `json:"node_push_interval"`
}
InviteConfig {
ForcedInvite bool `json:"forced_invite"`
ReferralPercentage int64 `json:"referral_percentage"`
OnlyFirstPurchase bool `json:"only_first_purchase"`
}
TelegramConfig {
TelegramBotToken string `json:"telegram_bot_token"`
TelegramGroupUrl string `json:"telegram_group_url"`
TelegramNotify bool `json:"telegram_notify"`
TelegramWebHookDomain string `json:"telegram_web_hook_domain"`
}
TosConfig {
TosContent string `json:"tos_content"`
}
PrivacyPolicyConfig {
PrivacyPolicy string `json:"privacy_policy"`
}
CurrencyConfig {
AccessKey string `json:"access_key"`
CurrencyUnit string `json:"currency_unit"`
CurrencySymbol string `json:"currency_symbol"`
}
SubscribeDiscount {
Quantity int64 `json:"quantity"`
Discount int64 `json:"discount"`
}
Subscribe {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
UnitPrice int64 `json:"unit_price"`
UnitTime string `json:"unit_time"`
Discount []SubscribeDiscount `json:"discount"`
Replacement int64 `json:"replacement"`
Inventory int64 `json:"inventory"`
Traffic int64 `json:"traffic"`
SpeedLimit int64 `json:"speed_limit"`
DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"`
Server []int64 `json:"server"`
Show bool `json:"show"`
Sell bool `json:"sell"`
Sort int64 `json:"sort"`
DeductionRatio int64 `json:"deduction_ratio"`
AllowDeduction bool `json:"allow_deduction"`
ResetCycle int64 `json:"reset_cycle"`
RenewalReset bool `json:"renewal_reset"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
SubscribeGroup {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Shadowsocks {
Method string `json:"method" validate:"required"`
Port int `json:"port" validate:"required"`
ServerKey string `json:"server_key"`
}
Vmess {
Port int `json:"port" validate:"required"`
Transport string `json:"transport" validate:"required"`
TransportConfig TransportConfig `json:"transport_config"`
Security string `json:"security" validate:"required"`
SecurityConfig SecurityConfig `json:"security_config"`
}
Vless {
Port int `json:"port" validate:"required"`
Flow string `json:"flow" validate:"required"`
Transport string `json:"transport" validate:"required"`
TransportConfig TransportConfig `json:"transport_config"`
Security string `json:"security" validate:"required"`
SecurityConfig SecurityConfig `json:"security_config"`
}
Trojan {
Port int `json:"port" validate:"required"`
Transport string `json:"transport" validate:"required"`
TransportConfig TransportConfig `json:"transport_config"`
Security string `json:"security" validate:"required"`
SecurityConfig SecurityConfig `json:"security_config"`
}
Hysteria2 {
Port int `json:"port" validate:"required"`
HopPorts string `json:"hop_ports" validate:"required"`
HopInterval int `json:"hop_interval" validate:"required"`
ObfsPassword string `json:"obfs_password" validate:"required"`
SecurityConfig SecurityConfig `json:"security_config"`
}
Tuic {
Port int `json:"port" validate:"required"`
SecurityConfig SecurityConfig `json:"security_config"`
}
SecurityConfig {
SNI string `json:"sni"`
AllowInsecure *bool `json:"allow_insecure"`
Fingerprint string `json:"fingerprint"`
RealityServerAddr string `json:"reality_server_addr"`
RealityServerPort int `json:"reality_server_port"`
RealityPrivateKey string `json:"reality_private_key"`
RealityPublicKey string `json:"reality_public_key"`
RealityShortId string `json:"reality_short_id"`
}
TransportConfig {
Path string `json:"path"`
Host string `json:"host"`
ServiceName string `json:"service_name"`
}
Server {
Id int64 `json:"id"`
Tags []string `json:"tags"`
Country string `json:"country"`
City string `json:"city"`
Name string `json:"name"`
ServerAddr string `json:"server_addr"`
RelayMode string `json:"relay_mode"`
RelayNode []NodeRelay `json:"relay_node"`
SpeedLimit int `json:"speed_limit"`
TrafficRatio float32 `json:"traffic_ratio"`
GroupId int64 `json:"group_id"`
Protocol string `json:"protocol"`
Config interface{} `json:"config"`
Enable *bool `json:"enable"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
Status *NodeStatus `json:"status"`
Sort int64 `json:"sort"`
}
OnlineUser {
SID int64 `json:"uid"`
IP string `json:"ip"`
}
NodeStatus {
Online interface{} `json:"online"`
Cpu float64 `json:"cpu"`
Mem float64 `json:"mem"`
Disk float64 `json:"disk"`
UpdatedAt int64 `json:"updated_at"`
}
ServerGroup {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
PaymentMethod {
Id int64 `json:"id"`
Name string `json:"name"`
Platform string `json:"platform"`
Description string `json:"description"`
Icon string `json:"icon"`
FeeMode uint `json:"fee_mode"`
FeePercent int64 `json:"fee_percent"`
FeeAmount int64 `json:"fee_amount"`
}
PaymentConfig {
Id int64 `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Platform string `json:"platform" validate:"required"`
Description string `json:"description"`
Icon string `json:"icon,omitempty"`
Domain string `json:"domain,omitempty"`
Config interface{} `json:"config" validate:"required"`
FeeMode uint `json:"fee_mode"`
FeePercent int64 `json:"fee_percent,omitempty"`
FeeAmount int64 `json:"fee_amount,omitempty"`
Enable *bool `json:"enable" validate:"required"`
}
PaymentMethodDetail {
Id int64 `json:"id"`
Name string `json:"name"`
Platform string `json:"platform"`
Description string `json:"description"`
Icon string `json:"icon"`
Domain string `json:"domain"`
Config interface{} `json:"config"`
FeeMode uint `json:"fee_mode"`
FeePercent int64 `json:"fee_percent"`
FeeAmount int64 `json:"fee_amount"`
Enable bool `json:"enable"`
NotifyURL string `json:"notify_url"`
}
Order {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
OrderNo string `json:"order_no"`
Type uint8 `json:"type"`
Quantity int64 `json:"quantity"`
Price int64 `json:"price"`
Amount int64 `json:"amount"`
GiftAmount int64 `json:"gift_amount"`
Discount int64 `json:"discount"`
Coupon string `json:"coupon"`
CouponDiscount int64 `json:"coupon_discount"`
Commission int64 `json:"commission,omitempty"`
Payment PaymentMethod `json:"payment"`
FeeAmount int64 `json:"fee_amount"`
TradeNo string `json:"trade_no"`
Status uint8 `json:"status"`
SubscribeId int64 `json:"subscribe_id"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
OrderDetail {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
OrderNo string `json:"order_no"`
Type uint8 `json:"type"`
Quantity int64 `json:"quantity"`
Price int64 `json:"price"`
Amount int64 `json:"amount"`
GiftAmount int64 `json:"gift_amount"`
Discount int64 `json:"discount"`
Coupon string `json:"coupon"`
CouponDiscount int64 `json:"coupon_discount"`
Commission int64 `json:"commission,omitempty"`
Payment PaymentMethod `json:"payment"`
Method string `json:"method"`
FeeAmount int64 `json:"fee_amount"`
TradeNo string `json:"trade_no"`
Status uint8 `json:"status"`
SubscribeId int64 `json:"subscribe_id"`
Subscribe Subscribe `json:"subscribe"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Document {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
Show bool `json:"show"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Coupon {
Id int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Count int64 `json:"count"`
Type uint8 `json:"type"`
Discount int64 `json:"discount"`
StartTime int64 `json:"start_time"`
ExpireTime int64 `json:"expire_time"`
UserLimit int64 `json:"user_limit"`
Subscribe []int64 `json:"subscribe"`
UsedCount int64 `json:"used_count"`
Enable bool `json:"enable"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Announcement {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Show *bool `json:"show"`
Pinned *bool `json:"pinned"`
Popup *bool `json:"popup"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
UserSubscribe {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
OrderId int64 `json:"order_id"`
SubscribeId int64 `json:"subscribe_id"`
Subscribe Subscribe `json:"subscribe"`
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"`
}
UserBalanceLog {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
Amount int64 `json:"amount"`
Type uint8 `json:"type"`
OrderId int64 `json:"order_id"`
Balance int64 `json:"balance"`
CreatedAt int64 `json:"created_at"`
}
UserAffiliate {
Avatar string `json:"avatar"`
Identifier string `json:"identifier"`
RegisteredAt int64 `json:"registered_at"`
Enable bool `json:"enable"`
}
SortItem {
Id int64 `json:"id" validate:"required"`
Sort int64 `json:"sort" validate:"required"`
}
TimePeriod {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Multiplier float32 `json:"multiplier"`
}
NodeRelay {
Host string `json:"host"`
Port int `json:"port"`
Prefix string `json:"prefix"`
}
ApplicationConfig {
AppId int64 `json:"app_id"`
EncryptionKey string `json:"encryption_key"`
EncryptionMethod string `json:"encryption_method"`
Domains []string `json:"domains" validate:"required"`
StartupPicture string `json:"startup_picture"`
StartupPictureSkipTime int64 `json:"startup_picture_skip_time"`
}
UserDevice {
Id int64 `json:"id"`
Ip string `json:"ip"`
Identifier string `json:"identifier"`
UserAgent string `json:"user_agent"`
Online bool `json:"online"`
Enabled bool `json:"enabled"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
UserAuthMethod {
AuthType string `json:"auth_type"`
AuthIdentifier string `json:"auth_identifier"`
Verified bool `json:"verified"`
}
AuthMethodConfig {
Id int64 `json:"id"`
Method string `json:"method"`
Config interface{} `json:"config"`
Enabled bool `json:"enabled"`
}
TrafficLog {
Id int64 `json:"id"`
ServerId int64 `json:"server_id"`
UserId int64 `json:"user_id"`
SubscribeId int64 `json:"subscribe_id"`
Download int64 `json:"download"`
Upload int64 `json:"upload"`
Timestamp int64 `json:"timestamp"`
}
ServerRuleGroup {
Id int64 `json:"id"`
Icon string `json:"icon"`
Name string `json:"name" validate:"required"`
Tags []string `json:"tags"`
Rules string `json:"rules"`
Enable bool `json:"enable"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
UserSubscribeLog {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
UserSubscribeId int64 `json:"user_subscribe_id"`
Token string `json:"token"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
CreatedAt int64 `json:"created_at"`
}
UserLoginLog {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
LoginIP string `json:"login_ip"`
UserAgent string `json:"user_agent"`
Success bool `json:"success"`
CreatedAt int64 `json:"created_at"`
}
MessageLog {
Id int64 `json:"id"`
Type string `json:"type"`
Platform string `json:"platform"`
To string `json:"to"`
Subject string `json:"subject"`
Content string `json:"content"`
Status int `json:"status"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Ads {
Id int `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Content string `json:"content"`
Description string `json:"description"`
TargetURL string `json:"target_url"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Status int `json:"status"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
//public order
PurchaseOrderRequest {
SubscribeId int64 `json:"subscribe_id"`
Quantity int64 `json:"quantity"`
Payment int64 `json:"payment,omitempty"`
Coupon string `json:"coupon,omitempty"`
}
PreOrderResponse {
Price int64 `json:"price"`
Amount int64 `json:"amount"`
Discount int64 `json:"discount"`
GiftAmount int64 `json:"gift_amount"`
Coupon string `json:"coupon"`
CouponDiscount int64 `json:"coupon_discount"`
FeeAmount int64 `json:"fee_amount"`
}
PurchaseOrderResponse {
OrderNo string `json:"order_no"`
}
RenewalOrderRequest {
UserSubscribeID int64 `json:"user_subscribe_id"`
Quantity int64 `json:"quantity"`
Payment int64 `json:"payment"`
Coupon string `json:"coupon,omitempty"`
}
RenewalOrderResponse {
OrderNo string `json:"order_no"`
}
ResetTrafficOrderRequest {
UserSubscribeID int64 `json:"user_subscribe_id"`
Payment int64 `json:"payment"`
}
ResetTrafficOrderResponse {
OrderNo string `json:"order_no"`
}
RechargeOrderRequest {
Amount int64 `json:"amount"`
Payment int64 `json:"payment"`
}
RechargeOrderResponse {
OrderNo string `json:"order_no"`
}
PreRenewalOrderResponse {
OrderNo string `json:"orderNo"`
}
CloseOrderRequest {
OrderNo string `json:"orderNo" validate:"required"`
}
QueryOrderDetailRequest {
OrderNo string `form:"order_no" validate:"required"`
}
StripePayment {
Method string `json:"method"`
ClientSecret string `json:"client_secret"`
PublishableKey string `json:"publishable_key"`
}
QueryOrderListRequest {
Page int `form:"page" validate:"required"`
Size int `form:"size" validate:"required"`
}
QueryOrderListResponse {
Total int64 `json:"total"`
List []OrderDetail `json:"list"`
}
//public document
QueryDocumentListResponse {
Total int64 `json:"total"`
List []Document `json:"list"`
}
QueryDocumentDetailRequest {
Id int64 `form:"id" validate:"required"`
}
// public payment
GetAvailablePaymentMethodsResponse {
List []PaymentMethod `json:"list"`
}
// public announcement
QueryAnnouncementRequest {
Page int `form:"page"`
Size int `form:"size"`
Pinned *bool `form:"pinned"`
Popup *bool `form:"popup"`
}
QueryAnnouncementResponse {
Total int64 `json:"total"`
List []Announcement `json:"announcements"`
}
//subscribe
QuerySubscribeListResponse {
List []Subscribe `json:"list"`
Total int64 `json:"total"`
}
QuerySubscribeGroupListResponse {
List []SubscribeGroup `json:"list"`
Total int64 `json:"total"`
}
GetUserSubscribeTrafficLogsRequest {
Page int `form:"page"`
Size int `form:"size"`
UserId int64 `form:"user_id"`
SubscribeId int64 `form:"subscribe_id"`
StartTime int64 `form:"start_time"`
EndTime int64 `form:"end_time"`
}
GetUserSubscribeTrafficLogsResponse {
List []TrafficLog `json:"list"`
Total int64 `json:"total"`
}
QueryUserAffiliateListResponse {
List []UserAffiliate `json:"list"`
Total int64 `json:"total"`
}
PlatformResponse {
List []PlatformInfo `json:"list"`
}
PlatformInfo {
Platform string `json:"platform"`
PlatformUrl string `json:"platform_url"`
PlatformFieldDescription map[string]string `json:"platform_field_description"`
}
AlipayNotifyResponse {
ReturnCode string `json:"return_code"`
}
EPayNotifyRequest {
Pid int64 `json:"pid" form:"pid"`
TradeNo string `json:"trade_no" form:"trade_no"`
OutTradeNo string `json:"out_trade_no" form:"out_trade_no"`
Type string `json:"type" form:"type"`
Name string `json:"name" form:"name"`
Money string `json:"money" form:"money"`
TradeStatus string `json:"trade_status" form:"trade_status"`
Param string `json:"param" form:"param"`
Sign string `json:"sign" form:"sign"`
SignType string `json:"sign_type" form:"sign_type"`
}
QueryUserAffiliateListRequest {
Page int `form:"page"`
Size int `form:"size"`
}
CheckoutOrderRequest {
OrderNo string `json:"orderNo"`
ReturnUrl string `json:"returnUrl,omitempty"`
}
CheckoutOrderResponse {
Type string `json:"type"`
CheckoutUrl string `json:"checkout_url,omitempty"`
Stripe *StripePayment `json:"stripe,omitempty"`
}
SiteCustomDataContacts {
Email string `json:"email"`
Telephone string `json:"telephone"`
Address string `json:"address"`
}
QueryUserAffiliateCountResponse {
Registers int64 `json:"registers"`
TotalCommission int64 `json:"total_commission"`
}
AppUserSubcbribe{
Id int64 `json:"id"`
Name string `json:"name"`
Upload int64 `json:"upload"`
Traffic int64 `json:"traffic"`
Download int64 `json:"download"`
DeviceLimit int64 `json:"device_limit"`
StartTime string `json:"start_time"`
ExpireTime string `json:"expire_time"`
List []AppUserSubscbribeNode `json:"list"`
}
AppUserSubscbribeNode{
Id int64 `json:"id"`
Name string `json:"name"`
Uuid string `json:"uuid"`
Protocol string `json:"protocol"`
RelayMode string `json:"relay_mode"`
RelayNode string `json:"relay_node"`
ServerAddr string `json:"server_addr"`
SpeedLimit int `json:"speed_limit"`
Tags []string `json:"tags"`
Traffic int64 `json:"traffic"`
TrafficRatio float64 `json:"traffic_ratio"`
Upload int64 `json:"upload"`
Config string `json:"config"`
Country string `json:"country"`
City string `json:"city"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
LatitudeCountry string `json:"latitudeCountry"`
LongitudeCountry string `json:"longitudeCountry"`
CreatedAt int64 `json:"created_at"`
Download int64 `json:"download"`
}
)

4
build.bat Normal file
View File

@ -0,0 +1,4 @@
SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build -o ppanel .\ppanel.go

30
cmd/root.go Normal file
View File

@ -0,0 +1,30 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(versionCmd)
}
var rootCmd = &cobra.Command{
Use: "PPanel",
Short: "PPanel is a modern multi-user agent panel.",
Long: `[ PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.]
[ Simple and easy to operate.]`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("args:", args)
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

155
cmd/run.go Normal file
View File

@ -0,0 +1,155 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/perfect-panel/ppanel-server/initialize"
"github.com/perfect-panel/ppanel-server/internal"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/conf"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/orm"
"github.com/perfect-panel/ppanel-server/pkg/service"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/perfect-panel/ppanel-server/queue"
"github.com/perfect-panel/ppanel-server/scheduler"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
func init() {
startCmd.Flags().StringVar(&startConfigPath, "config", "etc/ppanel.yaml", "ppanel.yaml directory to read from")
}
var (
startConfigPath string
)
var startCmd = &cobra.Command{
Use: "run",
Short: "start PPanel",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("[PPanel version] v" + constant.Version)
run()
},
}
func run() {
services := getServers()
defer services.Stop()
go services.Start()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-quit
}
func getServers() *service.Group {
var c config.Config
// check config file is exist
if _, err := os.Stat(startConfigPath); os.IsNotExist(err) {
// check directory is existed
if _, err := os.Stat("etc"); os.IsNotExist(err) {
logger.Errorf("Directory %s does not exist. Creating it...\n", "etc")
if err = os.MkdirAll("etc", os.ModePerm); err != nil {
log.Fatalf("Please create the directory %s and place the configuration file %s in it.\n", "etc", startConfigPath)
}
}
// create new config file
if _, err := os.Create(startConfigPath); err != nil {
logger.Errorf("Please create the configuration file %s in the directory %s.\n", startConfigPath, "etc")
panic(fmt.Sprintf("Please create the configuration file %s in the directory %s.\n", startConfigPath, "etc"))
}
}
// check config file is empty, if empty, start init web server
if initConfig(&c) {
status, server := initialize.Config(startConfigPath)
<-status
if err := server.Shutdown(context.TODO()); err != nil {
log.Printf("Init Server Shutdown: %s\n", err.Error())
}
}
conf.MustLoad(startConfigPath, &c)
if !c.Debug {
gin.SetMode(gin.ReleaseMode)
}
// init logger
if err := logger.SetUp(c.Logger); err != nil {
logger.Errorf("Logger setup failed: %v", err.Error())
}
// init service context
ctx := svc.NewServiceContext(c)
services := service.NewServiceGroup()
services.Add(internal.NewService(ctx))
services.Add(queue.NewService(ctx))
services.Add(scheduler.NewService(ctx))
return services
}
func initConfig(c *config.Config) bool {
// load config
conf.MustLoad(startConfigPath, c)
// check custom config
if startConfigPath != "etc/ppanel.yaml" && c.MySQL.Addr == "" {
return true
}
// check access secret
if c.JwtAuth.AccessSecret == "" && startConfigPath == "etc/ppanel.yaml" {
c.JwtAuth.AccessSecret = uuid.New().String()
// Get environment variables
dsn := os.Getenv("PPANEL_DB")
if dsn == "" {
return true
}
cfg := orm.ParseDSN(dsn)
if cfg == nil {
return true
} else {
c.MySQL = *cfg
}
// Get environment variables
uri := os.Getenv("PPANEL_REDIS")
if uri == "" {
return true
}
addr, pass, db, err := tool.ParseRedisURI(uri)
if err != nil {
return true
} else {
c.Redis.Host = addr
c.Redis.Pass = pass
c.Redis.DB = db
}
// save yaml file
newConfig := config.File{
Host: c.Host,
Port: c.Port,
Debug: c.Debug,
JwtAuth: c.JwtAuth,
Logger: c.Logger,
MySQL: c.MySQL,
Redis: c.Redis,
}
fileData, err := yaml.Marshal(newConfig)
if err != nil {
panic(err.Error())
}
// write to file
if err := os.WriteFile(startConfigPath, fileData, 0644); err != nil {
panic(err.Error())
}
}
return false
}

16
cmd/version.go Normal file
View File

@ -0,0 +1,16 @@
package cmd
import (
"fmt"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "PPanel version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("[PPanel version] " + config.Version)
},
}

88
doc/config-zh.md Normal file
View File

@ -0,0 +1,88 @@
### 配置文件说明
#### 1. 配置文件路径
配置文件默认路径为:`./etc/ppanel.yaml`,可通过启动参数 `--config` 指定配置文件路径。
#### 2. 配置文件格式
- 配置文件为yaml格式支持注释,命名为xxx.yaml。
```yaml
# 配置文件示例
Host: # 服务监听地址,默认: 0.0.0.0
Port: # 服务监听端口,默认: 8080
Debug: # 是否开启调试模式,开启后无法使用后台日志功能, 默认: false
JwtAuth: # JWT认证配置
AccessSecret: # 访问令牌密钥, 默认: 随机生成
AccessExpire: # 访问令牌过期时间,单位秒, 默认: 604800
Logger: # 日志配置
FilePath: # 日志文件路径, 默认: ./ppanel.log
MaxSize: # 日志文件最大大小,单位MB, 默认: 50
MaxBackup: # 日志文件最大备份数, 默认: 3
MaxAge: # 日志文件最大保存时间,单位天, 默认: 30
Compress: # 是否压缩日志文件, 默认: true
Level: # 日志级别, 默认: info, 可选: debug, info, warn, error, panic, panic, fatal
MySQL:
Addr: # MySQL地址, 必填
Username: # MySQL用户名, 必填
Password: # MySQL密码, 必填
Dbname: # MySQL数据库名, 必填
Config: # Mysql配置默认值 charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
MaxIdleConns: # 最大空闲连接数, 默认: 10
MaxOpenConns: # 最大打开连接数, 默认: 100
LogMode: # 日志级别, 默认: info, 可选: debug, error, warn, info
LogZap: # 是否使用zap日志记录sql, 默认: true
SlowThreshold: # 慢查询阈值,单位毫秒, 默认: 1000
Redis:
Host: # Redis地址, 默认:localhost:6379
Pass: # Redis密码, 默认: ""
DB: # Redis数据库, 默认: 0
Administer:
Email: # 后台登录邮箱, 默认: admin@ppanel.dev
Password: # 后台登录密码, 默认: password
```
#### 3. 配置文件说明
- `Host`: 服务监听地址,默认: **0.0.0.0**
- `Port`: 服务监听端口,默认: **8080**
- `Debug`: 是否开启调试模式,开启后无法使用后台日志功能, 默认: **false**
- `JwtAuth`: JWT认证配置
- `AccessSecret`: 访问令牌密钥, 默认: **随机生成**
- `AccessExpire`: 访问令牌过期时间,单位秒, 默认: **604800**
- `Logger`: 日志配置
- `FilePath`: 日志文件路径, 默认: **./ppanel.log**
- `MaxSize`: 日志文件最大大小,单位MB, 默认: **50**
- `MaxBackup`: 日志文件最大备份数, 默认: **3**
- `MaxAge`: 日志文件最大保存时间,单位天, 默认: **30**
- `Compress`: 是否压缩日志文件, 默认: **true**
- `Level`: 日志级别, 默认: **info**, 可选: **debug, info, warn, error, panic, panic, fatal**
- `MySQL`: MySQL配置
- `Addr`: MySQL地址, 必填
- `Username`: MySQL用户名, 必填
- `Password`: MySQL密码, 必填
- `Dbname`: MySQL数据库名, 必填
- `Config`: Mysql配置默认值 charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
- `MaxIdleConns`: 最大空闲连接数, 默认: **10**
- `MaxOpenConns`: 最大打开连接数, 默认: **100**
- `LogMode`: 日志级别, 默认: **info**, 可选: **debug, error, warn, info**
- `LogZap`: 是否使用zap日志记录sql, 默认: **true**
- `SlowThreshold`: 慢查询阈值,单位毫秒, 默认: **1000**
- `Redis`: Redis配置
- `Host`: Redis地址, 默认: **localhost:6379**
- `Pass`: Redis密码, 默认: **""**
- `DB`: Redis数据库, 默认: **0**
- `Administer`: 后台登录配置
- `Email`: 后台登录邮箱, 默认: **admin@ppanel.dev**
- `Password`: 后台登录密码, 默认: **password**
#### 4. 环境变量
支持的环境变量如下:
| 环境变量 | 配置项 | 示例 |
|--------------|---------|:-------------------------------------------|
| PPANEL_DB | MySQL配置 | root:password@tcp(localhost:3306)/vpnboard |
| PPANEL_REDIS | Redis配置 | redis://localhost:6379" |

116
doc/config.md Normal file
View File

@ -0,0 +1,116 @@
### Configuration File Instructions
#### Configuration File Path
The default configuration file path is ./etc/ppanel.yaml. You can specify a custom path using the --config startup parameter.
#### Configuration File Format
The configuration file uses the YAML format, supports comments, and should be named xxx.yaml.
```yaml
# Sample Configuration File
Host: # Service listening address, default: 0.0.0.0
Port: # Service listening port, default: 8080
Debug: # Enable debug mode; disables backend logging when enabled, default: false
JwtAuth: # JWT authentication settings
AccessSecret: # Access token secret, default: randomly generated
AccessExpire: # Access token expiration time in seconds, default: 604800
Logger: # Logging configuration
FilePath: # Log file path, default: ./ppanel.log
MaxSize: # Maximum log file size in MB, default: 50
MaxBackup: # Maximum number of log file backups, default: 3
MaxAge: # Maximum log file retention time in days, default: 30
Compress: # Whether to compress log files, default: true
Level: # Logging level, default: info; options: debug, info, warn, error, panic, fatal
MySQL:
Addr: # MySQL address, required
Username: # MySQL username, required
Password: # MySQL password, required
Dbname: # MySQL database name, required
Config: # MySQL configuration, default: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
MaxIdleConns: # Maximum idle connections, default: 10
MaxOpenConns: # Maximum open connections, default: 100
LogMode: # Log level, default: info; options: debug, error, warn, info
LogZap: # Whether to use zap for SQL logging, default: true
SlowThreshold: # Slow query threshold in milliseconds, default: 1000
Redis:
Host: # Redis address, default: localhost:6379
Pass: # Redis password, default: ""
DB: # Redis database, default: 0
Administer:
Email: # Admin login email, default: admin@ppanel.dev
Password: # Admin login password, default: password
```
#### 3.Configuration Descriptions
- Host: Service listening address, default: 0.0.0.0
- Port: Service listening port, default: 8080
- Debug: Enable debug mode; disables backend logging when enabled, default: false
- JwtAuth: JWT authentication settings
- AccessSecret: Access token secret, default: randomly generated
- AccessExpire: Access token expiration time in seconds, default: 604800
- Logger: Logging configuration
- FilePath: Log file path, default: ./ppanel.log
- MaxSize: Maximum log file size in MB, default: 50
- MaxBackup: Maximum number of log file backups, default: 3
- MaxAge: Maximum log file retention time in days, default: 30
- Compress: Whether to compress log files, default: true
- Level: Logging level, default: info; options: debug, info, warn, error, panic, fatal
- MySQL: MySQL configuration
- Addr: MySQL address, required
- Username: MySQL username, required
- Password: MySQL password, required
- Dbname: MySQL database name, required
- Config: MySQL configuration, default: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
- MaxIdleConns: Maximum idle connections, default: 10
- MaxOpenConns: Maximum open connections, default: 100
- LogMode: Log level, default: info; options: debug, error, warn, info
- LogZap: Whether to use zap for SQL logging, default: true
- SlowThreshold: Slow query threshold in milliseconds, default: 1000
- Redis: Redis configuration
- Host: Redis address, default: localhost:6379
- Pass: Redis password, default: ""
- DB: Redis database, default: 0
- Administer: Admin login configuration
- Email: Admin login email, default: admin@ppanel.dev
- Password: Admin login password, default: password
#### 4. Environment Variables
Supported environment variables are as follows:
| Environment Variable | Configuration | Example |
|-----------------------|---------------|:-------------------------------------------|
| PPANEL_DB | MySQL config | root:password@tcp(localhost:3306)/vpnboard |
| PPANEL_REDIS | Redis config | redis://localhost:6379" |

133
doc/install-zh.md Normal file
View File

@ -0,0 +1,133 @@
### 安装说明
#### 前置系统要求
- Mysql 5.7+ (推荐使用8.0)
- Redis 6.0+ (推荐使用7.0)
#### 二进制安装
1.确定系统架构,并下载对应的二进制文件
下载地址:`https://github.com/perfect-panel/ppanel/releases`
示例说明系统Linux amd64用户root当前目录/root
- 下载二进制文件
```shell
$ wget https://github.com/perfect-panel/ppanel/releases/download/v0.1.0/ppanel-server-linux-amd64.tar.gz
```
- 解压二进制文件
```shell
$ tar -zxvf ppanel-server-linux-amd64.tar.gz
```
- 进入解压后的目录
```shell
$ cd ppanel-server-linux-amd64
```
- 赋予二进制文件执行权限
```shell
$ chmod +x ppanel-server
```
- 创建 systemd 服务文件
```shell
$ cat > /etc/systemd/system/ppanel.service <<EOF
[Unit]
Description=PPANEL Server
After=network.target
[Service]
ExecStart=/root/ppanel-server-linux-amd64/ppanel-server
Restart=always
User=root
WorkingDirectory=/root/ppanel-server-linux-amd64
[Install]
WantedBy=multi-user.target
EOF
```
- 重新加载 systemd 服务
```shell
$ systemctl daemon-reload
```
- 启动服务
```shell
$ systemctl start ppanel
```
##### 其他说明
1. 安装路径:二进制文件将解压到 /root/ppanel-server-linux-amd64 目录下
2. systemd 服务:
- 服务名称ppanel
- 服务配置文件:/etc/systemd/system/ppanel.service
- 服务启动命令systemctl start ppanel
- 服务停止命令systemctl stop ppanel
- 服务重启命令systemctl restart ppanel
- 服务状态命令systemctl status ppanel
- 服务开机自启systemctl enable ppanel
3. 设置开机自启可通过以下命令开机自启
```shell
$ systemctl enable ppanel
```
4. 服务日志:服务日志默认输出到 /root/ppanel-server-linux-amd64/ppanel.log 文件中
5. 可通过 `journalctl -u ppanel -f` 查看服务日志
6. 当配置文件为空或者不存在的情况下,服务会使用默认配置启动,配置文件路径为:`./etc/ppanel.yaml`
请通过`http://服务器地址:8080/init` 初始化系统配置
#### NGINX 反向代理配置
以下是反向代理配置示例,将 `ppanel` 服务代理到 `api.ppanel.dev` 域名下
```nginx
server {
listen 80;
server_name ppanel.dev;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
#Set Nginx Cache
set $static_filezbsQiET1 0;
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
{
set $static_filezbsQiET1 1;
expires 1m;
}
if ( $static_filezbsQiET1 = 0 )
{
add_header Cache-Control no-cache;
}
}
}
```
如果使用cloudflare代理服务需要获取到用户真实访问IP。请在Nginx配置文件中Http段落中加入:
- 需要依赖:**ngx_http_realip_module**模块, 使用nginx -V命令查看nginx是否已经编译该模块没有的话需要自己编译。
```nginx
# cloudflare Start
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# cloudflare END
```

142
doc/install.md Normal file
View File

@ -0,0 +1,142 @@
### Installation Instructions
#### Prerequisites
- MySQL 5.7+ (recommended: 8.0)
- Redis 6.0+ (recommended: 7.0)
#### Binary Installation
1. Determine your system architecture and download the corresponding binary file.
Download URL: `https://github.com/perfect-panel/ppanel/releases`
Example setup: OS: Linux amd64, User: root, Current directory: `/root`
- Download the binary file:
```shell
$ wget https://github.com/perfect-panel/ppanel/releases/download/v0.1.0/ppanel-server-linux-amd64.tar.gz
```
- Extract the binary file:
```shell
$ tar -zxvf ppanel-server-linux-amd64.tar.gz
```
- Navigate to the extracted directory:
```shell
$ cd ppanel-server-linux-amd64
```
- Grant execution permissions to the binary:
```shell
$ chmod +x ppanel
```
- Create a systemd service file:
```shell
$ cat > /etc/systemd/system/ppanel.service <<EOF
[Unit]
Description=PPANEL Server
After=network.target
[Service]
ExecStart=/root/ppanel-server-linux-amd64/ppanel
Restart=always
User=root
WorkingDirectory=/root/ppanel-server-linux-amd64
[Install]
WantedBy=multi-user.target
EOF
```
- Reload the systemd service configuration:
```shell
$ systemctl daemon-reload
```
- Start the service:
```shell
$ systemctl start ppanel
```
#### Additional Notes
1. Installation Path: The binary files will be extracted to /root/ppanel-server-linux-amd64.
2. systemd Service:
- Service Name: ppanel
- Service Configuration File: /etc/systemd/system/ppanel.service
- Service Commands:
- Start: systemctl start ppanel
- Stop: systemctl stop ppanel
- Restart: systemctl restart ppanel
- Status: systemctl status ppanel
- Enable on Boot: systemctl enable ppanel
3. Enable Auto-start: Use the following command to enable the service on boot:
```shell
$ systemctl enable ppanel
```
4. Service Logs: By default, logs are output to `/root/ppanel-server-linux-amd64/ppanel.log`.
5. You can view service logs using: `journalctl -u ppanel -f`
6. If the configuration file is missing or empty, the service will start with default settings. The configuration file path is `./etc/ppanel.yaml`. Access `http://<server_address>:8080/init` to **initialize the system configuration**.
#### NGINX Reverse Proxy Configuration
Below is an example configuration to proxy the ppanel service to the domain api.ppanel.dev:
```nginx
server {
listen 80;
server_name ppanel.dev;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
# Set Nginx Cache
set $static_file_cache 0;
if ($uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$") {
set $static_file_cache 1;
expires 1m;
}
if ($static_file_cache = 0) {
add_header Cache-Control no-cache;
}
}
}
```
If using Cloudflare as a proxy service, you need to retrieve the user's real IP address. Add the following to the http section of the NGINX configuration file:
- Dependency: `ngx_http_realip_module`. Check if your NGINX build includes this module by running `nginx -V`. If not, you will need to recompile NGINX with this module.
```nginx
# Cloudflare Start
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Cloudflare End
```

13
docker-compose.yml Normal file
View File

@ -0,0 +1,13 @@
version: '3'
services:
ppanel:
container_name: ppanel-server
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./etc/ppanel.yaml:/app/etc/ppanel.yaml
restart: always

BIN
generate/gopure-amd64.exe Normal file

Binary file not shown.

BIN
generate/gopure-arm64.exe Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
generate/gopure-linux-amd64 Normal file

Binary file not shown.

BIN
generate/gopure-linux-arm64 Normal file

Binary file not shown.

135
go.mod Normal file
View File

@ -0,0 +1,135 @@
module github.com/perfect-panel/ppanel-server
go 1.23.3
require (
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f
github.com/alibabacloud-go/darabonba-openapi v0.1.18
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.18
github.com/alibabacloud-go/tea v1.2.2
github.com/alicebob/miniredis/v2 v2.34.0
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72
github.com/andybalholm/brotli v1.1.1
github.com/forgoer/openssl v1.6.0
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.24.0
github.com/go-resty/resty/v2 v2.15.3
github.com/go-sql-driver/mysql v1.8.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/gofrs/uuid/v5 v5.3.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/hibiken/asynq v0.24.1
github.com/jinzhu/copier v0.4.0
github.com/klauspost/compress v1.17.7
github.com/nyaruka/phonenumbers v1.5.0
github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.6.1
github.com/smartwalle/alipay/v3 v3.2.23
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0
github.com/stripe/stripe-go/v81 v81.1.0
github.com/twilio/twilio-go v1.23.11
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0
go.opentelemetry.io/otel/exporters/zipkin v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0
golang.org/x/oauth2 v0.25.0
golang.org/x/time v0.6.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
gorm.io/plugin/soft_delete v1.2.1
k8s.io/apimachinery v0.31.1
)
require (
github.com/fatih/color v1.18.0
github.com/goccy/go-json v0.10.4
github.com/spaolacci/murmur3 v1.1.0
google.golang.org/grpc v1.61.1
google.golang.org/protobuf v1.36.3
)
require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bytedance/sonic v1.12.7 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.6 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/openzipkin/zipkin-go v0.4.2 // 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/robfig/cron/v3 v3.0.1 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.0.9 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

505
go.sum Normal file
View File

@ -0,0 +1,505 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/darabonba-openapi v0.1.18 h1:3eUVmAr7WCJp7fgIvmCd9ZUyuwtJYbtUqJIed5eXCmk=
github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc=
github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.18 h1:hfZA4cgIl6frNdsRmAyj8sn9J1bihQpYbzIVv2T/+Cs=
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.18/go.mod h1:di54xjBFHvKiQQo7st3TUmiMy0ywne5TOHup786Rhes=
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28=
github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72 h1:a93gW7OBt55SksMQVibqPWdu4Ly73KM4d3zoIUUX3cs=
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72/go.mod h1:PsJICrlruG9QcJDYuZ0dO/2KtMDALzRbony8NkxZ2nE=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.5.6 h1:Jm4VaCI/+Ug5Q57IzEoZbwx4iQFA6wkXv72juUSeK+g=
github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/forgoer/openssl v1.6.0 h1:IueL+UfH0hKo99xFPojHLlO3QzRBQqFY+Cht0WwtOC0=
github.com/forgoer/openssl v1.6.0/go.mod h1:9DZ4yOsQmveP0aXC/BpQ++Y5TKaz5yR9+emcxmIZNZs=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8=
github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw=
github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nyaruka/phonenumbers v1.5.0 h1:0M+Gd9zl53QC4Nl5z1Yj1O/zPk2XXBUwR/vlzdXSJv4=
github.com/nyaruka/phonenumbers v1.5.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E=
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/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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stripe/stripe-go/v81 v81.1.0 h1:OlpGPO2vhS2raLR/NuvHKeRUZ57FTkdZBTcd5Hhoyos=
github.com/stripe/stripe-go/v81 v81.1.0/go.mod h1:C/F4jlmnGNacvYtBp/LUHCvVUJEZffFQCobkzwY1WOo=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/twilio/twilio-go v1.23.11 h1:Q532m0rgWF1AzzF4Z4ejzTk5XeORWT+zLGzlklSk/iU=
github.com/twilio/twilio-go v1.23.11/go.mod h1:zRkMjudW7v7MqQ3cWNZmSoZJ7EBjPZ4OpNh2zm7Q6ko=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/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.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
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-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
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-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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
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.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

277
initialize/config.go Normal file
View File

@ -0,0 +1,277 @@
package initialize
import (
"database/sql"
"embed"
"fmt"
"html/template"
"log"
"net/http"
"os"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/driver/mysql"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/pkg/conf"
"github.com/perfect-panel/ppanel-server/pkg/orm"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
)
//go:embed templates/*.html
var templateFS embed.FS
var initStatus = make(chan bool)
var configPath string
func Config(path string) (chan bool, *http.Server) {
// Set the configuration file path
configPath = path
// Create a new Gin instance
r := gin.Default()
// Create a new HTTP server
server := &http.Server{
Addr: ":8080",
Handler: r,
}
// Load templates
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
r.SetHTMLTemplate(tmpl)
r.GET("/init", handleInit)
r.POST("/init/config", handleInitConfig)
r.POST("/init/mysql/test", HandleMySQLTest)
r.POST("/init/redis/test", HandleRedisTest)
// Handle 404
r.NoRoute(func(c *gin.Context) {
c.Redirect(http.StatusFound, "/init")
})
go func(server *http.Server) {
// Start the server
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}(server)
return initStatus, server
}
func handleInit(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
func handleInitConfig(c *gin.Context) {
// Load configuration file
var cfg config.File
conf.MustLoad(configPath, &cfg)
var request struct {
AdminEmail string `json:"adminEmail"`
AdminPassword string `json:"adminPassword"`
MysqlHost string `json:"mysqlHost"`
MysqlPort string `json:"mysqlPort"`
MysqlDatabase string `json:"mysqlDatabase"`
MysqlUser string `json:"mysqlUser"`
MysqlPassword string `json:"mysqlPassword"`
RedisHost string `json:"redisHost"`
RedisPort string `json:"redisPort"`
RedisPassword string `json:"redisPassword"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
cfg.Debug = false
// jwt secret
cfg.JwtAuth.AccessSecret = uuid.New().String()
// mysql
cfg.MySQL.Addr = fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort)
cfg.MySQL.Dbname = request.MysqlDatabase
cfg.MySQL.Username = request.MysqlUser
cfg.MySQL.Password = request.MysqlPassword
// redis
cfg.Redis.Host = fmt.Sprintf("%s:%s", request.RedisHost, request.RedisPort)
cfg.Redis.Pass = request.RedisPassword
// save config
fileData, err := yaml.Marshal(cfg)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Configuration initialization failed",
"data": nil,
})
c.Abort()
return
}
// create mysql connection
db, err := orm.ConnectMysql(orm.Mysql{
Config: orm.Config{
Addr: fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort),
Username: request.MysqlUser,
Password: request.MysqlPassword,
Dbname: request.MysqlDatabase,
Config: "charset%3Dutf8mb4%26parseTime%3Dtrue%26loc%3DLocal",
MaxIdleConns: 10,
MaxOpenConns: 10,
SlowThreshold: 1000,
},
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "MySQL connection failed",
"data": nil,
})
c.Abort()
return
}
// init
if err := initMysql(db, request.AdminEmail, request.AdminPassword); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "MySQL initialization failed",
"data": nil,
})
c.Abort()
return
}
// write to file
if err := os.WriteFile(configPath, fileData, 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Configuration initialization failed",
"data": nil,
})
c.Abort()
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "Configuration initialized",
"status": true,
})
initStatus <- true
}
func HandleMySQLTest(c *gin.Context) {
var request struct {
Host string `json:"host"`
Port string `json:"port"`
Database string `json:"database"`
User string `json:"user"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.User, request.Password, request.Host, request.Port, request.Database)
var status = true
var message string
var tx *sql.DB
var tables []string
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
logger.Errorf("connect mysql failed, err: %v\n", err.Error())
status = false
message = "MySQL connection failed"
goto result
}
tx, _ = db.DB()
if err := tx.Ping(); err != nil {
logger.Errorf("ping mysql failed, err: %v\n", err.Error())
status = false
message = "MySQL connection failed"
}
tables, err = db.Migrator().GetTables()
if err != nil {
logger.Errorf("database table check failed, err: %v\n", err.Error())
status = false
message = "Database table check failed"
goto result
}
if len(tables) > 0 {
status = false
message = "The database contains existing data. Please clear it before proceeding with the installation."
goto result
}
result:
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": message,
"status": status,
})
}
func HandleRedisTest(c *gin.Context) {
var request struct {
Host string `json:"host"`
Port string `json:"port"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
if err := tool.RedisPing(fmt.Sprintf("%s:%s", request.Host, request.Port), request.Password, 0); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": nil,
"status": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": nil,
"status": true,
})
}
func initMysql(tx *gorm.DB, email, password string) error {
tables, err := tx.Migrator().GetTables()
if err != nil {
return fmt.Errorf("database table validation failed: %w", err)
}
if len(tables) > 0 {
return errors.New("the database contains existing data. Please clear it before proceeding with the installation")
}
if err := migrate.InitPPanelSQL(tx); err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
}
if err := migrate.CreateAdminUser(email, password, tx); err != nil {
return fmt.Errorf("failed to create admin user: %w", err)
}
return nil
}

33
initialize/email.go Normal file
View File

@ -0,0 +1,33 @@
package initialize
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
// Email get email smtp config
func Email(ctx *svc.ServiceContext) {
logger.Debug("Email config initialization")
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "email")
if err != nil {
panic(fmt.Sprintf("failed to find email auth method: %v", err.Error()))
}
var cfg config.EmailConfig
var emailConfig = new(auth.EmailAuthConfig)
if err := emailConfig.Unmarshal(method.Config); err != nil {
panic(fmt.Sprintf("failed to unmarshal email auth config: %v", err.Error()))
}
tool.DeepCopy(&cfg, emailConfig)
cfg.Enable = *method.Enabled
value, _ := json.Marshal(emailConfig.PlatformConfig)
cfg.PlatformConfig = string(value)
ctx.Config.Email = cfg
}

22
initialize/init.go Normal file
View File

@ -0,0 +1,22 @@
package initialize
import "github.com/perfect-panel/ppanel-server/internal/svc"
func StartInitSystemConfig(svc *svc.ServiceContext) {
// Initialize the system configuration
Mysql(svc)
VerifyVersion(svc)
Site(svc)
Node(svc)
Email(svc)
Invite(svc)
Verify(svc)
Subscribe(svc)
Register(svc)
Mobile(svc)
TrafficDataToRedis(svc)
if !svc.Config.Debug {
Telegram(svc)
}
}

23
initialize/invite.go Normal file
View File

@ -0,0 +1,23 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Invite(ctx *svc.ServiceContext) {
// Initialize the system configuration
logger.Debug("Register config initialization")
configs, err := ctx.SystemModel.GetInviteConfig(context.Background())
if err != nil {
logger.Error("[Init Invite Config] Get Invite Config Error: ", logger.Field("error", err.Error()))
return
}
var inviteConfig config.InviteConfig
tool.SystemConfigSliceReflectToStruct(configs, &inviteConfig)
ctx.Config.Invite = inviteConfig
}

View File

@ -0,0 +1,54 @@
-- 先检查 `email` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'email';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `email`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `telephone` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `telephone_area_code` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone_area_code';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone_area_code`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_email` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_email';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_email`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_telephone` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_telephone_area_code` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone_area_code';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone_area_code`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -0,0 +1,118 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查表是否存在,如果存在则跳过创建
CREATE TABLE IF NOT EXISTS `oauth_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'platform',
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
`redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Redirect URL',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_oauth_config_platform` (`platform`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 插入记录时忽略重复记录
BEGIN;
INSERT IGNORE INTO `oauth_config` (`id`, `platform`, `config`, `redirect`, `enabled`, `created_at`, `updated_at`) VALUES
(1, 'apple', '{\"team_id\":\"\",\"key_id\":\"\",\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(2, 'google', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(3, 'github', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(4, 'facebook', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(5, 'telegram', '{\"bot\":\"\",\"bot_token\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292');
COMMIT;
-- 检测更新设置表
BEGIN;
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES
('sms', 'SmsEnabled', 'false', 'bool', '是否启用短信功能', NOW(), NOW()),
('sms', 'SmsKey', 'your-key', 'string', '短信服务用户名或Key',NOW(), NOW()),
('sms', 'SmsSecret', 'your-secret', 'string', '短信服务密码或Secret', NOW(), NOW()),
('sms', 'SmsSign', 'your-sign', 'string', '短信签名', NOW(), NOW()),
('sms', 'SmsTemplate', 'your-template', 'string', '短信模板ID', NOW(), NOW()),
('sms', 'SmsRegion', 'cn-hangzhou', 'string', '短信服务所在区域(适用于阿里云)', NOW(), NOW()),
('sms', 'SmsTemplate', '您的验证码是{{.Code}}请在5分钟内使用。', 'string', '自定义短信模板', NOW(), NOW()),
('sms', 'SmsTemplateCode', 'SMS_12345678', 'string', '阿里云国内短信模板代码',NOW(),NOW()),
('sms', 'SmsTemplateParam', '{\"code\":{{.Code}}}', 'string', '短信模板参数', NOW(), NOW()),
('sms', 'SmsPlatform', 'smsbao', 'string', '当前使用的短信平台', NOW(), NOW()),
('sms', 'SmsLimit', '10', 'int64', '可以发送的短信最大数量', NOW(), NOW()),
('sms', 'SmsInterval', '60', 'int64', '发送短信的时间间隔(单位:秒)',NOW(), NOW()),
('sms', 'SmsExpireTime', '300', 'int64', '短信验证码的过期时间(单位:秒)',NOW(), NOW()),
('email', 'EmailEnabled', 'true', 'bool', '启用邮箱登陆',NOW(), NOW()),
('email', 'EmailSmtpHost', '', 'string', '邮箱服务器地址', NOW(), NOW()),
('email', 'EmailSmtpPort', '465', 'int', '邮箱服务器端口',NOW(), NOW()),
('email', 'EmailSmtpUser', 'domain@f1shyu.com', 'string', '邮箱服务器用户名', NOW(), NOW()),
('email', 'EmailSmtpPass', 'password', 'string', '邮箱服务器密码', NOW(), NOW()),
('email', 'EmailSmtpFrom', 'domain@f1shyu.com', 'string', '发送邮件的邮箱',NOW(), NOW()),
('email', 'EmailSmtpSSL', 'true', 'bool', '邮箱服务器加密方式',NOW(), NOW()),
('email', 'EmailTemplate', '%s', 'string', '邮件模板',NOW(), NOW()),
('email', 'VerifyEmailTemplate', '', 'string', 'Verify Email template',NOW(), NOW()),
('email', 'MaintenanceEmailTemplate', '', 'string', 'Maintenance Email template',NOW(), NOW()),
('email', 'ExpirationEmailTemplate', '', 'string', 'Expiration Email template', NOW(), NOW()),
('email', 'EmailEnableVerify', 'true', 'bool', '是否开启邮箱验证', NOW(), NOW()),
('email', 'EmailEnableDomainSuffix', 'false', 'bool', '是否开启邮箱域名后缀限制',NOW(), NOW()),
('email', 'EmailDomainSuffixList', 'qq.com', 'string', '邮箱域名后缀列表',NOW(), NOW());
COMMIT;
-- User Device
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`device_number` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Number.',
`online` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`last_online` datetime(3) DEFAULT NULL COMMENT 'Last Online',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Mobile
CREATE TABLE IF NOT EXISTS `sms` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_general_ci,
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(1) DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Application Config
CREATE TABLE IF NOT EXISTS `application_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
`domains` text COLLATE utf8mb4_general_ci,
`startup_picture` text COLLATE utf8mb4_general_ci,
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Application Version
CREATE TABLE IF NOT EXISTS `application_version` (
`id` bigint NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `fk_application_application_versions` (`application_id`),
CONSTRAINT `fk_application_application_versions` FOREIGN KEY (`application_id`) REFERENCES `application` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
UPDATE `subscribe` SET `unit_time`='Month' WHERE unit_time = '';
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,44 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user_device`;
-- User Device
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server_rule_group
-- ----------------------------
DROP TABLE IF EXISTS `server_rule_group`;
CREATE TABLE `server_rule_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Records of server_rule_group
-- ----------------------------
BEGIN;
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,562 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for ads
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ads` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads title',
`type` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads type',
`content` text COLLATE utf8mb4_german2_ci COMMENT 'Ads content',
`target_url` varchar(512) COLLATE utf8mb4_german2_ci DEFAULT '' COMMENT 'Ads target url',
`start_time` datetime DEFAULT NULL COMMENT 'Ads start time',
`end_time` datetime DEFAULT NULL COMMENT 'Ads end time',
`status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci;
-- ----------------------------
-- Table structure for announcement
-- ----------------------------
CREATE TABLE IF NOT EXISTS `announcement` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show',
`pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned',
`popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用名称',
`icon` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用图标',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`subscribe_type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application_config
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
`domains` text COLLATE utf8mb4_general_ci,
`startup_picture` text COLLATE utf8mb4_general_ci,
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
`invitation_link` text COLLATE utf8mb4_general_ci COMMENT 'Invitation Link',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application_version
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application_version` (
`id` bigint NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `fk_application_application_versions` (`application_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for coupon
-- ----------------------------
CREATE TABLE IF NOT EXISTS `coupon` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name',
`code` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code',
`count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount',
`start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time',
`expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time',
`user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit',
`subscribe` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit',
`used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_coupon_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for document
-- ----------------------------
CREATE TABLE IF NOT EXISTS `document` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Document Content',
`tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags',
`show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for auth_method
-- ----------------------------
CREATE TABLE IF NOT EXISTS `auth_method` (
`id` bigint NOT NULL AUTO_INCREMENT,
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method',
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_auth_method` (`method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for order
-- ----------------------------
CREATE TABLE IF NOT EXISTS `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id',
`order_no` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge',
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
`price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price',
`amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount',
`gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount',
`coupon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon',
`coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount',
`commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission',
`payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id',
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method',
`fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount',
`trade_no` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished',
`subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id',
`subscribe_token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token',
`is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_order_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for payment
-- ----------------------------
CREATE TABLE IF NOT EXISTS `payment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name',
`platform` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Payment Description',
`icon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon',
`domain` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain',
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration',
`fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount',
`fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage',
`fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount',
`enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_payment_token` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server
-- ----------------------------
CREATE TABLE IF NOT EXISTS `server` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
`tags` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
`country` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country',
`city` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City',
`latitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude',
`longitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude',
`server_addr` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address',
`relay_mode` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode',
`relay_node` text COLLATE utf8mb4_general_ci COMMENT 'Relay Node',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`traffic_ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio',
`group_id` bigint DEFAULT NULL COMMENT 'Group ID',
`protocol` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
`config` text COLLATE utf8mb4_general_ci COMMENT 'Config',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server_group
-- ----------------------------
CREATE TABLE IF NOT EXISTS `server_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Group Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for sms
-- ----------------------------
CREATE TABLE IF NOT EXISTS `sms` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_general_ci,
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(1) DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description',
`unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price',
`unit_time` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time',
`discount` text COLLATE utf8mb4_general_ci COMMENT 'Discount',
`replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement',
`inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory',
`traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit',
`quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota',
`group_id` bigint DEFAULT NULL COMMENT 'Group Id',
`server_group` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server Group',
`server` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page',
`sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio',
`allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction',
`reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly',
`renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe_group
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Group Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe_type
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe_type` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
`mark` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅标识',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for system
-- ----------------------------
CREATE TABLE IF NOT EXISTS `system` (
`id` bigint NOT NULL AUTO_INCREMENT,
`category` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category',
`key` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name',
`value` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value',
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type',
`desc` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_system_key` (`key`),
KEY `index_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for ticket
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ticket` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Description',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for ticket_follow
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ticket_follow` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId',
`from` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for traffic_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `traffic_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`server_id` bigint NOT NULL COMMENT 'Server ID',
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time',
PRIMARY KEY (`id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_server_id` (`server_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`password` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password',
`avatar` text COLLATE utf8mb4_general_ci COMMENT 'User Avatar',
`balance` bigint DEFAULT '0' COMMENT 'User Balance',
`telegram` bigint DEFAULT NULL COMMENT 'Telegram Account',
`refer_code` varchar(20) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code',
`referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID',
`commission` bigint DEFAULT '0' COMMENT 'Commission',
`gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin',
`valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified',
`enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications',
`enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications',
`enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications',
`enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications',
`enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications',
`enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time',
`is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted',
PRIMARY KEY (`id`),
KEY `idx_referer` (`referer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_auth_methods
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_auth_methods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`auth_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone',
`auth_identifier` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier',
`verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
UNIQUE KEY `idx_auth_identifier` (`auth_identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_balance_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_balance_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`amount` bigint NOT NULL COMMENT 'Amount',
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward',
`order_id` bigint DEFAULT NULL COMMENT 'Order ID',
`balance` bigint NOT NULL COMMENT 'Balance',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_commission_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_commission_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
`amount` bigint NOT NULL COMMENT 'Amount',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_device
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_gift_amount_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_gift_amount_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID',
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce',
`amount` bigint NOT NULL COMMENT 'Amount',
`balance` bigint NOT NULL COMMENT 'Balance',
`remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_subscribe
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`order_id` bigint NOT NULL COMMENT 'Order ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time',
`expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time',
`traffic` bigint DEFAULT '0' COMMENT 'Traffic',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token',
`uuid` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID',
`status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`finished_at` datetime(3) DEFAULT NULL COMMENT 'Finished At',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_user_subscribe_token` (`token`),
UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`),
KEY `idx_user_id` (`user_id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_token` (`token`),
KEY `idx_uuid` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `server_rule_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_login_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_login_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`login_ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP',
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
`success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_subscribe_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_subscribe_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID',
`token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token',
`ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP',
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_user_subscribe_id` (`user_subscribe_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `message_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform',
`to` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To',
`subject` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_device_online_record
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_device_online_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NULL DEFAULT NULL COMMENT 'User ID',
`identifier` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'Device Identifier',
`online_time` datetime(3) NULL DEFAULT NULL COMMENT 'Online Time',
`offline_time` datetime(3) NULL DEFAULT NULL COMMENT 'Offline Time',
`online_seconds` bigint NOT NULL DEFAULT '0' COMMENT 'Online Seconds ',
`duration_days` bigint NOT NULL DEFAULT '0' COMMENT 'Duration Days ',
`created_at` datetime(3) NULL DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

644
initialize/migrate/init.go Normal file
View File

@ -0,0 +1,644 @@
package migrate
import (
"fmt"
"time"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/payment"
"github.com/perfect-panel/ppanel-server/internal/model/subscribeType"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/email"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/sms"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/perfect-panel/ppanel-server/pkg/uuidx"
"gorm.io/gorm"
)
func InitPPanelSQL(db *gorm.DB) error {
logger.Info("PPanel SQL initialization started")
startTime := time.Now()
defer func() {
logger.Info("PPanel SQL initialization completed", logger.Field("duration", time.Since(startTime).String()))
}()
return db.Transaction(func(tx *gorm.DB) error {
var err error
defer func() {
// If an error occurs, delete all tables
if err != nil {
logger.Debugf("PPanel SQL initialization completed, err: %v", err.Error())
tables, _ := tx.Migrator().GetTables()
for _, table := range tables {
tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table))
}
}
}()
// init ppanel.sql file
if err = ExecuteSQLFile(tx, "database/ppanel.sql"); err != nil {
return err
}
//Insert basic system data
if err = insertBasicSystemData(tx); err != nil {
return err
}
// insert into OAuth config
if err = insertAuthMethodConfig(tx); err != nil {
return err
}
// insert into Payment config
if err = insertPaymentConfig(tx); err != nil {
return err
}
// insert into SubscribeType
if err = insertSubscribeType(tx); err != nil {
return err
}
return err
})
}
func insertBasicSystemData(tx *gorm.DB) error {
if err := insertSiteConfig(tx); err != nil {
return err
}
if err := insertSubscribeConfig(tx); err != nil {
return err
}
if err := insertVerifyConfig(tx); err != nil {
return err
}
if err := insertSeverConfig(tx); err != nil {
return err
}
if err := insertInviteConfig(tx); err != nil {
return err
}
if err := insertRegisterConfig(tx); err != nil {
return err
}
if err := insertCurrencyConfig(tx); err != nil {
return err
}
if err := insertVerifyCodeConfig(tx); err != nil {
return err
}
version := system.System{
Category: "system",
Key: "Version",
Value: constant.Version,
Type: "string",
Desc: "System Version",
}
if err := tx.Model(&system.System{}).Save(&version).Error; err != nil {
return err
}
return nil
}
// insertSiteConfig
func insertSiteConfig(tx *gorm.DB) error {
siteConfig := []system.System{
{
Category: "site",
Key: "SiteLogo",
Value: "/favicon.svg",
Type: "string",
Desc: "Site Logo",
},
{
Category: "site",
Key: "SiteName",
Value: "Perfect Panel",
Type: "string",
Desc: "Site Name",
},
{
Category: "site",
Key: "SiteDesc",
Value: "PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.",
Type: "string",
Desc: "Site Description",
},
{
Category: "site",
Key: "Host",
Value: "",
Type: "string",
Desc: "Site Host",
},
{
Category: "site",
Key: "Keywords",
Value: "Perfect Panel,PPanel",
Type: "string",
Desc: "Site Keywords",
},
{
Category: "site",
Key: "CustomHTML",
Value: "",
Type: "string",
Desc: "Custom HTML",
},
{
Category: "site",
Key: "CustomData",
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
Type: "string",
Desc: "Custom data",
},
{
Category: "tos",
Key: "TosContent",
Value: "Welcome to use Perfect Panel",
Type: "string",
Desc: "Terms of Service",
},
{
Category: "tos",
Key: "PrivacyPolicy",
Value: "",
Type: "string",
Desc: "PrivacyPolicy",
},
{
Category: "ad",
Key: "WebAD",
Value: "false",
Type: "bool",
Desc: "Display ad on the web",
},
}
return tx.Model(&system.System{}).Save(&siteConfig).Error
}
// insertSubscribeConfig
func insertSubscribeConfig(tx *gorm.DB) error {
subscribeConfig := []system.System{
{
Category: "subscribe",
Key: "SingleModel",
Value: "false",
Type: "bool",
Desc: "是否单订阅模式",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
Category: "subscribe",
Key: "SubscribePath",
Value: "/api/subscribe",
Type: "string",
Desc: "订阅路径",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
Category: "subscribe",
Key: "SubscribeDomain",
Value: "",
Type: "string",
Desc: "订阅域名",
},
{
Category: "subscribe",
Key: "PanDomain",
Value: "false",
Type: "bool",
Desc: "是否使用泛域名",
},
}
return tx.Model(&system.System{}).Save(&subscribeConfig).Error
}
// insertVerifyConfig
func insertVerifyConfig(tx *gorm.DB) error {
verifyConfig := []system.System{
{
Category: "verify",
Key: "TurnstileSiteKey",
Value: "",
Type: "string",
Desc: "TurnstileSiteKey",
},
{
Category: "verify",
Key: "TurnstileSecret",
Value: "",
Type: "string",
Desc: "TurnstileSecret",
},
{
Category: "verify",
Key: "EnableLoginVerify",
Value: "false",
Type: "bool",
Desc: "is enable login verify",
},
{
Category: "verify",
Key: "EnableRegisterVerify",
Value: "false",
Type: "bool",
Desc: "is enable register verify",
},
{
Category: "verify",
Key: "EnableResetPasswordVerify",
Value: "false",
Type: "bool",
Desc: "is enable reset password verify",
},
}
return tx.Model(&system.System{}).Save(&verifyConfig).Error
}
// insertSeverConfig
func insertSeverConfig(tx *gorm.DB) error {
serverConfig := []system.System{
{
Category: "server",
Key: "NodeSecret",
Value: "12345678",
Type: "string",
Desc: "node secret",
},
{
Category: "server",
Key: "NodePullInterval",
Value: "10",
Type: "int",
Desc: "node pull interval",
},
{
Category: "server",
Key: "NodePushInterval",
Value: "60",
Type: "int",
Desc: "node push interval",
},
{
Category: "server",
Key: "NodeMultiplierConfig",
Value: "[]",
Type: "string",
Desc: "node multiplier config",
},
}
return tx.Model(&system.System{}).Save(&serverConfig).Error
}
// insertInviteConfig
func insertInviteConfig(tx *gorm.DB) error {
inviteConfig := []system.System{
{
Category: "invite",
Key: "ForcedInvite",
Value: "false",
Type: "bool",
Desc: "Forced invite",
},
{
Category: "invite",
Key: "ReferralPercentage",
Value: "20",
Type: "int",
Desc: "Referral percentage",
},
{
Category: "invite",
Key: "OnlyFirstPurchase",
Value: "false",
Type: "bool",
Desc: "Only first purchase",
},
}
return tx.Model(&system.System{}).Save(&inviteConfig).Error
}
// insertRegisterConfig
func insertRegisterConfig(tx *gorm.DB) error {
registerConfig := []system.System{
{
Category: "register",
Key: "StopRegister",
Value: "false",
Type: "bool",
Desc: "is stop register",
},
{
Category: "register",
Key: "EnableTrial",
Value: "false",
Type: "bool",
Desc: "is enable trial",
},
{
Category: "register",
Key: "TrialSubscribe",
Value: "",
Type: "int",
Desc: "Trial subscription",
},
{
Category: "register",
Key: "TrialTime",
Value: "24",
Type: "int",
Desc: "Trial time",
},
{
Category: "register",
Key: "TrialTimeUnit",
Value: "Hour",
Type: "string",
Desc: "Trial time unit",
},
{
Category: "register",
Key: "EnableIpRegisterLimit",
Value: "false",
Type: "bool",
Desc: "is enable IP register limit",
},
{
Category: "register",
Key: "IpRegisterLimit",
Value: "3",
Type: "int",
Desc: "IP Register Limit",
},
{
Category: "register",
Key: "IpRegisterLimitDuration",
Value: "64",
Type: "int",
Desc: "IP Register Limit Duration (minutes)",
},
}
return tx.Model(&system.System{}).Save(&registerConfig).Error
}
// insertAuthMethodConfig
func insertAuthMethodConfig(tx *gorm.DB) error {
// insert into OAuth config
var methods []auth.Auth
methods = append(methods, []auth.Auth{
initEmailConfig(),
initMobileConfig(),
{
Method: "apple",
Config: new(auth.AppleAuthConfig).Marshal(),
},
{
Method: "google",
Config: new(auth.GoogleAuthConfig).Marshal(),
},
{
Method: "github",
Config: new(auth.GithubAuthConfig).Marshal(),
},
{
Method: "facebook",
Config: new(auth.FacebookAuthConfig).Marshal(),
},
{
Method: "telegram",
Config: new(auth.TelegramAuthConfig).Marshal(),
},
{
Method: "device",
Config: new(auth.DeviceConfig).Marshal(),
},
}...)
return tx.Model(&auth.Auth{}).Save(&methods).Error
}
// insertPaymentConfig
func insertPaymentConfig(tx *gorm.DB) error {
enable := true
payments := []payment.Payment{
{
Id: -1,
Name: "Balance",
Platform: "balance",
Icon: "",
Domain: "",
Config: "",
FeeMode: 0,
FeePercent: 0,
FeeAmount: 0,
Enable: &enable,
},
}
// reset auto increment
if err := tx.Exec("ALTER TABLE `payment` AUTO_INCREMENT = 1").Error; err != nil {
logger.Errorw("Reset auto increment failed", logger.Field("error", err))
return err
}
return tx.Model(&payment.Payment{}).Save(&payments).Error
}
// insertSubscribeType
func insertSubscribeType(tx *gorm.DB) error {
// insert into subscribe type
var subscribeTypes []subscribeType.SubscribeType
subscribeTypes = append(subscribeTypes, []subscribeType.SubscribeType{
{
Name: "Clash",
Mark: "Clash",
},
{
Name: "Hiddify",
Mark: "Hiddify",
},
{
Name: "Loon",
Mark: "Loon",
},
{
Name: "NekoBox",
Mark: "NekoBox",
},
{
Name: "NekoRay",
Mark: "NekoRay",
},
{
Name: "Netch",
Mark: "Netch",
},
{
Name: "Quantumult",
Mark: "Quantumult",
},
{
Name: "Shadowrocket",
Mark: "Shadowrocket",
},
{
Name: "Singbox",
Mark: "Singbox",
},
{
Name: "Surfboard",
Mark: "Surfboard",
},
{
Name: "Surge",
Mark: "Surge",
},
{
Name: "V2box",
Mark: "V2box",
},
{
Name: "V2rayN",
Mark: "V2rayN",
},
{
Name: "V2rayNg",
Mark: "V2rayNg",
},
}...)
// insert into payment
return tx.Save(&subscribeTypes).Error
}
// CreateAdminUser create admin user
func CreateAdminUser(email, password string, tx *gorm.DB) error {
enable := true
return tx.Transaction(func(tx *gorm.DB) error {
// Prevent duplicate creation
if tx.Model(&user.User{}).Find(&user.User{}).RowsAffected != 0 {
logger.Info("User already exists, skip creating administrator account")
return nil
}
u := user.User{
Password: tool.EncodePassWord(password),
IsAdmin: &enable,
ReferCode: uuidx.UserInviteCode(time.Now().Unix()),
}
if err := tx.Model(&user.User{}).Save(&u).Error; err != nil {
return err
}
method := user.AuthMethods{
UserId: u.Id,
AuthType: "email",
AuthIdentifier: email,
Verified: true,
}
if err := tx.Model(&user.AuthMethods{}).Save(&method).Error; err != nil {
return err
}
return nil
})
}
func initEmailConfig() auth.Auth {
enable := true
smtpConfig := new(auth.SMTPConfig)
emailConfig := auth.EmailAuthConfig{
Platform: "smtp",
PlatformConfig: smtpConfig,
EnableVerify: false,
EnableDomainSuffix: false,
DomainSuffixList: "",
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate,
}
authMethod := auth.Auth{
Method: "email",
Config: emailConfig.Marshal(),
Enabled: &enable,
}
return authMethod
}
func initMobileConfig() auth.Auth {
cfg := new(auth.AlibabaCloudConfig)
mobileConfig := auth.MobileAuthConfig{
Platform: sms.AlibabaCloud.String(),
PlatformConfig: cfg,
EnableWhitelist: false,
Whitelist: make([]string, 0),
}
authMethod := auth.Auth{
Method: "mobile",
Config: mobileConfig.Marshal(),
}
return authMethod
}
// insert into currency config
func insertCurrencyConfig(tx *gorm.DB) error {
currencyConfig := []system.System{
{
Category: "currency",
Key: "Currency",
Value: "USD",
Type: "string",
Desc: "Currency",
},
{
Category: "currency",
Key: "CurrencySymbol",
Value: "$",
Type: "string",
Desc: "Currency Symbol",
},
{
Category: "currency",
Key: "CurrencyUnit",
Value: "USD",
Type: "string",
Desc: "Currency Unit",
},
{
Category: "currency",
Key: "AccessKey",
Value: "",
Type: "string",
Desc: "Exchangerate Access Key",
},
}
return tx.Model(&system.System{}).Save(&currencyConfig).Error
}
// insert into verify code config
func insertVerifyCodeConfig(tx *gorm.DB) error {
verifyCodeConfig := []system.System{
{
Category: "verify_code",
Key: "VerifyCodeExpireTime",
Value: "300",
Type: "int",
Desc: "Verify code expire time",
},
{
Category: "verify_code",
Key: "VerifyCodeLimit",
Value: "15",
Type: "int",
Desc: "limits of verify code",
},
{
Category: "verify_code",
Key: "VerifyCodeInterval",
Value: "60",
Type: "int",
Desc: "Interval of verify code",
},
}
return tx.Model(&system.System{}).Save(&verifyCodeConfig).Error
}

View File

@ -0,0 +1,37 @@
package migrate
import (
"testing"
"github.com/perfect-panel/ppanel-server/pkg/orm"
"gorm.io/gorm"
)
func connMySQL() *gorm.DB {
cfg := orm.Config{
Addr: "127.0.0.1",
Username: "root",
Password: "mylove520",
Dbname: "ppanel",
}
db, err := orm.ConnectMysql(orm.Mysql{
Config: cfg,
})
if err != nil {
return nil
}
return db
}
func TestInitPPanelSQL(t *testing.T) {
t.Skipf("Skip TestInitPPanelSQL")
db := connMySQL()
if db == nil {
t.Error("connect mysql failed")
return
}
if err := InitPPanelSQL(db); err != nil {
t.Error(err)
}
t.Logf("InitPPanelSQL success")
}

View File

@ -0,0 +1,34 @@
package migrate
import (
"embed"
"time"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
//go:embed database/*.sql
var sqlFiles embed.FS
func Migrate(ctx *svc.ServiceContext) {
logger.Debug("SQL Migrate started")
startTime := time.Now()
defer func() {
logger.WithDuration(time.Since(startTime)).Debug("PPanel SQL Migrate completed")
}()
db := ctx.DB
if !db.Migrator().HasTable(&system.System{}) {
if err := InitPPanelSQL(db); err != nil {
logger.Error("SQL Migrate failed", logger.Field("err", err.Error()))
panic(err)
}
// create admin user
if err := CreateAdminUser(ctx.Config.Administrator.Email, ctx.Config.Administrator.Password, db); err != nil {
logger.Error("Create admin User failed", logger.Field("err", err.Error()))
panic(err)
}
}
}

View File

@ -0,0 +1,456 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/model/application"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/log"
"github.com/perfect-panel/ppanel-server/internal/model/server"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/email"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/sms"
"gorm.io/gorm"
)
func Migrate01200(db *gorm.DB) error {
var version = "0.1.2(01200)"
return db.Transaction(func(tx *gorm.DB) error {
if exists := db.Migrator().HasColumn(&user.OldUser{}, "email"); !exists {
logger.Debug("Migrate 01200 skipped", logger.Field("reason", "old user table not exists"))
return nil
}
logger.Debug("Migrate 01200 started", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
var users []*user.OldUser
if err := tx.Model(&user.OldUser{}).Find(&users).Error; err != nil {
return err
}
if err := tx.Migrator().AutoMigrate(&user.AuthMethods{}); err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "create user auth methods table"), logger.Field("error", err.Error()))
return err
}
err := tx.Transaction(func(tx *gorm.DB) error {
for _, oldUser := range users {
if oldUser.Email == "" {
continue
}
// create user auth method
authMethod := &user.AuthMethods{
UserId: oldUser.Id,
AuthType: "email",
AuthIdentifier: oldUser.Email,
Verified: false,
}
if err := tx.Create(authMethod).Error; err != nil {
return err
}
}
return nil
})
if err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"), logger.Field("error", err.Error()))
return err
}
logger.Debug("Migrate 01200 completed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
logger.Debug("Migrate 01200 started", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
// exclude sql files
if err := migrate.ExecuteSQLFile(tx, "database/01200-patch.sql"); err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
return err
}
logger.Debug("Migrate 01200 completed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
logger.Debug("Migrate 01200 started", logger.Field("step", "3"), logger.Field("action", "update system config"))
versionConfig := &system.System{
Category: "system",
Key: "Version",
Value: version,
Type: "string",
Desc: "Version of the system, eg: 1.0.0(10000)",
}
// update system config
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Save(&versionConfig).Error; err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "3"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01201(db *gorm.DB) error {
version := "0.1.2(01201)"
// exclude sql files
if err := migrate.ExecuteSQLFile(db, "database/01201-patch.sql"); err != nil {
logger.Errorw("Migrate 01201 failed", logger.Field("step", "1"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
return err
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01201 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
}
func Migrate01202(db *gorm.DB) error {
version := "0.1.2(01202)"
return db.Transaction(func(tx *gorm.DB) error {
// migrate email config to system config
if err := db.Migrator().AutoMigrate(&auth.Auth{}); err != nil {
logger.Errorw("Migrate01202: AutoMigrate Auth failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
if db.Migrator().HasColumn("oauth_config", "platform") {
if err := db.Migrator().RenameColumn("oauth_config", "platform", "method"); err != nil {
logger.Errorw("Migrate01202: RenameColumn platform to method failed", logger.Field("version", version), logger.Field("error", err.Error()))
}
}
// init email config
if err := initEmailConfig(db); err != nil {
logger.Errorw("Migrate01202: initEmailConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// init mobile config
if err := initMobileConfig(db); err != nil {
logger.Errorw("Migrate01202: initMobileConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// drop oauth_config table
err := db.Migrator().DropTable("oauth_config")
if err != nil {
logger.Debug("Migrate01202: DropTable oauth_config failed", logger.Field("version", version), logger.Field("error", err.Error()))
}
// exclude sql files
if err := migrate.ExecuteSQLFile(db, "database/01202-patch.sql"); err != nil {
logger.Errorw("Migrate 01202 failed", logger.Field("action", "exclude sql files"), logger.Field("file", "database/012002-patch.sql"), logger.Field("error", err.Error()))
return err
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01202 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01203(db *gorm.DB) error {
version := "0.1.2(01203)"
return db.Transaction(func(tx *gorm.DB) error {
if err := db.AutoMigrate(&user.LoginLog{}, &user.SubscribeLog{}); err != nil {
logger.Errorw("Migrate01203: AutoMigrate LoginLog/SubscribeLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01203: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01204(db *gorm.DB) error {
version := "0.1.2(01204)"
return db.Transaction(func(tx *gorm.DB) error {
if err := db.AutoMigrate(&log.MessageLog{}); err != nil {
logger.Errorw("Migrate01204: AutoMigrate MessageLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// Trial configuration
if err := initTrialConfig(tx); err != nil {
logger.Errorw("Migrate01204: initTrialConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// Add auth method with device
if err := addAuthMethodWithDevice(tx); err != nil {
logger.Errorw("Migrate01204: Add auth method with device failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01204: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01205(db *gorm.DB) error {
version := "0.1.2(01205)"
return db.Transaction(func(tx *gorm.DB) error {
// Add VerifyCode public configuration
configs := []system.System{
{
Category: "verify_code",
Key: "VerifyCodeExpireTime",
Value: "5",
Type: "int",
Desc: "Verify code expire time",
},
{
Category: "verify_code",
Key: "VerifyCodeLimit",
Value: "15",
Type: "int",
Desc: "limits of verify code",
},
{
Category: "verify_code",
Key: "VerifyCodeInterval",
Value: "60",
Type: "int",
Desc: "Interval of verify code",
},
}
if err := tx.Model(&system.System{}).Save(&configs).Error; err != nil {
logger.Errorw("Migrate01205: Save VerifyCode public configuration failed", logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01301(db *gorm.DB) error {
version := "0.1.3(01301)"
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Migrator().AlterColumn(&application.Application{}, "icon")
if err != nil {
logger.Errorw("Migrate01301: AlterColumn failed", logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01602(db *gorm.DB) error {
version := "0.1.6(01602)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'TosContent'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "tos",
Key: "TosContent",
Value: "Welcome to use Perfect Panel",
Type: "string",
Desc: "Terms of Service",
}).Error; err != nil {
return err
}
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01701(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
version := "0.1.7(01701)"
if err := db.Migrator().AlterColumn(&user.User{}, "Avatar"); err != nil {
return err
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01702(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
version := "0.1.7(01702)"
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'Keywords'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "Keywords",
Value: "Perfect Panel,PPanel",
Type: "string",
Desc: "Keywords",
}).Error; err != nil {
return err
}
}
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomHTML'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "CustomHTML",
Value: "",
Type: "string",
Desc: "Custom HTML",
}).Error; err != nil {
return err
}
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01703(db *gorm.DB) error {
version := "0.1.7(01703)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'PrivacyPolicy'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "tos",
Key: "PrivacyPolicy",
Value: "",
Type: "string",
Desc: "Privacy Policy",
}).Error; err != nil {
return err
}
}
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
})
}
func Migrate01704(db *gorm.DB) error {
version := "0.1.7(01704)"
// check server table latitude column exists, if not exists, create it
if exists := db.Migrator().HasColumn(&server.Server{}, "latitude"); !exists {
if err := db.Migrator().AddColumn(&server.Server{}, "latitude"); err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add latitude column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "add latitude column"))
}
// check server table longitude column exists, if not exists, create it
if exists := db.Migrator().HasColumn(&server.Server{}, "longitude"); !exists {
if err := db.Migrator().AddColumn(&server.Server{}, "longitude"); err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add longitude column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "add longitude column"))
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "update system config"))
return nil
}
func Migrate01705(db *gorm.DB) error {
version := "0.1.7(01705)"
// check user_device table exists, if not exists, create it
if exists := db.Migrator().HasTable(&user.Device{}); !exists {
if err := db.Migrator().CreateTable(&user.Device{}); err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("action", "create user_device table"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01705 success", logger.Field("action", "create user_device table"))
}
// check user_table exists and imei column exists, if exists, update imei column name to identifier
if exists := db.Migrator().HasColumn(&user.Device{}, "imei"); exists {
if err := db.Migrator().RenameColumn(&user.Device{}, "imei", "identifier"); err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("action", "rename imei column to identifier"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01705 success", logger.Field("action", "rename imei column to identifier"))
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
}
func initMobileConfig(db *gorm.DB) error {
cfg := new(auth.AlibabaCloudConfig)
mobileConfig := auth.MobileAuthConfig{
Platform: sms.AlibabaCloud.String(),
PlatformConfig: cfg.Marshal(),
EnableWhitelist: false,
Whitelist: make([]string, 0),
}
authMethod := auth.Auth{
Method: "mobile",
Config: mobileConfig.Marshal(),
}
if err := db.Save(&authMethod).Error; err != nil {
return err
}
return nil
}
func initEmailConfig(db *gorm.DB) error {
enable := true
smtpConfig := new(auth.SMTPConfig)
emailConfig := auth.EmailAuthConfig{
Platform: "smtp",
PlatformConfig: smtpConfig.Marshal(),
EnableVerify: false,
EnableDomainSuffix: false,
DomainSuffixList: "",
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
}
authMethod := auth.Auth{
Method: "email",
Config: emailConfig.Marshal(),
Enabled: &enable,
}
return db.Save(&authMethod).Error
}
func initTrialConfig(tx *gorm.DB) error {
configs := []system.System{
{
Category: "register",
Key: "TrialSubscribe",
Value: "",
Type: "int",
Desc: "Trial subscription",
},
{
Category: "register",
Key: "TrialTime",
Value: "24",
Type: "int",
Desc: "Trial time",
},
{
Category: "register",
Key: "TrialTimeUnit",
Value: "Hour",
Type: "string",
Desc: "Trial time unit",
},
}
return tx.Model(&system.System{}).Save(&configs).Error
}
func addAuthMethodWithDevice(tx *gorm.DB) error {
return tx.Model(&auth.Auth{}).Save(&auth.Auth{
Method: "device",
}).Error
}

View File

@ -0,0 +1,252 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/internal/model/ads"
"github.com/perfect-panel/ppanel-server/internal/model/application"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/order"
"github.com/perfect-panel/ppanel-server/internal/model/payment"
"github.com/perfect-panel/ppanel-server/internal/model/server"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
)
func Migrate02000(db *gorm.DB) error {
version := "0.2.0(02000)"
return db.Transaction(func(tx *gorm.DB) error {
if err := initDeviceConfig(tx); err != nil {
logMigrationError("Setting Device Config", err)
return err
}
logMigrationSuccess("Setting Device Config")
if !tx.Migrator().HasTable(&ads.Ads{}) {
if err := createAdsTable(tx); err != nil {
return err
}
}
if err := updatePaymentTable(tx); err != nil {
return err
}
if err := tx.Migrator().AutoMigrate(&order.Order{}); err != nil {
logMigrationError("Auto Migrate Order", err)
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02001(db *gorm.DB) error {
version := "0.2.0(02001)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "CustomData",
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
Type: "string",
Desc: "Custom data",
}).Error; err != nil {
logMigrationError("create custom data system config", err)
return err
}
}
return updateSystemVersion(tx, version)
})
}
func Migrate02002(db *gorm.DB) error {
version := "0.2.0(02002)"
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").UpdateColumn("type", "string").Error; err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02003(db *gorm.DB) error {
version := "0.2.0(02003)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &order.Order{}, "payment_id"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "platform"); err != nil {
return err
}
if err := dropColumnIfExists(tx, &payment.Payment{}, "mark"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "description"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "token"); err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02007(db *gorm.DB) error {
version := "0.2.0(02007)"
return db.Transaction(func(tx *gorm.DB) error {
if err := recreateTable(tx, &server.RuleGroup{}); err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02008(db *gorm.DB) error {
version := "0.2.0(02008)"
return db.Transaction(func(tx *gorm.DB) error {
if exists := tx.Migrator().HasColumn(&application.ApplicationConfig{}, "invitation_link"); !exists {
if err := tx.Migrator().AddColumn(&application.ApplicationConfig{}, "invitation_link"); err != nil {
logger.Errorw("Migrate 02008 failed", logger.Field("action", "add invitation_link column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 02008 success", logger.Field("action", "add invitation_link column"))
}
if exists := tx.Migrator().HasTable(&user.DeviceOnlineRecord{}); !exists {
if err := tx.Migrator().CreateTable(&user.DeviceOnlineRecord{}); err != nil {
logger.Errorw("Migrate 02008 failed", logger.Field("action", "create device_online_record table"), logger.Field("error", err.Error()))
return err
}
}
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
})
}
func Migrate02009(db *gorm.DB) error {
version := "0.2.0(02009)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
logger.Errorw("Migrate 02009 failed", logger.Field("action", "subscribe table add finished_at column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02010(db *gorm.DB) error {
version := "0.2.0(02010)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil {
logger.Errorw("Migrate 02010 failed", logger.Field("action", "application_config table add kr_website_id column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02011(db *gorm.DB) error {
version := "0.2.0(02011)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "used_period"); err != nil {
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add used_period column"), logger.Field("error", err.Error()))
return err
}
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "total_period"); err != nil {
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add total_period column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func initDeviceConfig(db *gorm.DB) error {
cfg := new(auth.DeviceConfig)
return db.Model(&auth.Auth{}).Where("method = ?", "device").Update("config", cfg.Marshal()).Error
}
func createAdsTable(tx *gorm.DB) error {
if err := tx.Migrator().CreateTable(&ads.Ads{}); err != nil {
logMigrationError("Create Table Ads", err)
return err
}
logMigrationSuccess("Create Table Ads")
return tx.Model(&system.System{}).Save(&system.System{
Category: "ad",
Key: "WebAD",
Value: "false",
Type: "bool",
Desc: "Display ad on the web",
}).Error
}
func updatePaymentTable(tx *gorm.DB) error {
if err := tx.Exec("DROP TABLE IF EXISTS `payment`").Error; err != nil {
logMigrationError("Drop Payment Table", err)
}
if err := tx.AutoMigrate(&payment.Payment{}); err != nil {
logMigrationError("Auto Migrate Payment", err)
return err
}
enable := true
return tx.Model(&payment.Payment{}).Create(&payment.Payment{
Id: -1,
Name: "",
Platform: "balance",
Icon: "",
Domain: "",
Config: "",
FeeMode: 0,
FeePercent: 0,
FeeAmount: 0,
Enable: &enable,
}).Error
}
func updateSystemVersion(tx *gorm.DB, version string) error {
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
}
func addColumnIfNotExists(tx *gorm.DB, model interface{}, columnName string) error {
if exists := tx.Migrator().HasColumn(model, columnName); !exists {
if err := tx.Migrator().AddColumn(model, columnName); err != nil {
logMigrationError("add "+columnName+" column", err)
return err
}
logMigrationSuccess("add " + columnName + " column")
}
return nil
}
func dropColumnIfExists(tx *gorm.DB, model interface{}, columnName string) error {
if exists := tx.Migrator().HasColumn(model, columnName); exists {
if err := tx.Migrator().DropColumn(model, columnName); err != nil {
logMigrationError("del "+columnName+" column", err)
return err
}
logMigrationSuccess("del " + columnName + " column")
}
return nil
}
func recreateTable(tx *gorm.DB, model interface{}) error {
if exists := tx.Migrator().HasTable(model); exists {
if err := tx.Migrator().DropTable(model); err != nil {
logMigrationError("drop table", err)
return err
}
}
if err := tx.Migrator().CreateTable(model); err != nil {
logMigrationError("create table", err)
return err
}
return nil
}
func logMigrationError(action string, err error) {
logger.Errorw("Migration failed", logger.Field("action", action), logger.Field("error", err.Error()))
}
func logMigrationSuccess(action string) {
logger.Infow("Migration success", logger.Field("action", action))
}

View File

@ -0,0 +1,30 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/internal/model/application"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
)
func Migrate03001(db *gorm.DB) error {
version := "0.3.0(1)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
logger.Errorw("Migrate 03001 failed", logger.Field("action", "user.Subscribe table add finished_at column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate03002(db *gorm.DB) error {
version := "0.3.0(2)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil {
logger.Errorw("Migrate 03002 failed", logger.Field("action", "application.Config table add kr_website_id column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}

101
initialize/migrate/tool.go Normal file
View File

@ -0,0 +1,101 @@
package migrate
import (
"fmt"
"strings"
"gorm.io/gorm"
)
// ExecuteSQLFile 执行嵌入的 SQL 文件,去除注释
func ExecuteSQLFile(tx *gorm.DB, path string) error {
// 读取 SQL 文件内容
sqlContent, err := sqlFiles.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read embedded SQL file: %v", err)
}
// 清除注释内容
cleanedSQL := removeComments(string(sqlContent))
// 将清除注释后的内容按分号分割成多个 SQL 语句
sqlStatements := splitSQLStatements(cleanedSQL)
// 遍历 SQL 语句并执行
for _, stmt := range sqlStatements {
stmt = strings.TrimSpace(stmt)
if stmt == "" {
continue
}
// 执行 SQL 语句
if err := tx.Exec(stmt).Error; err != nil {
return fmt.Errorf("failed to execute SQL statement: %v \nSQL: %s", err.Error(), stmt)
}
}
return nil
}
// removeComments 去除 SQL 代码中的注释
func removeComments(sql string) string {
var result strings.Builder
inSingleLineComment := false
inMultiLineComment := false
length := len(sql)
for i := 0; i < length; i++ {
// 处理 -- 单行注释
if !inMultiLineComment && !inSingleLineComment && i+1 < length && sql[i] == '-' && sql[i+1] == '-' {
inSingleLineComment = true
i++ // 跳过 '-'
continue
}
// 结束单行注释(支持 \r\n 和 \n
if inSingleLineComment && (sql[i] == '\n' || sql[i] == '\r') {
inSingleLineComment = false
}
// 处理 /* 多行注释 */
if !inSingleLineComment && !inMultiLineComment && i+1 < length && sql[i] == '/' && sql[i+1] == '*' {
inMultiLineComment = true
i++ // 跳过 '*'
continue
}
// 结束多行注释
if inMultiLineComment && i+1 < length && sql[i] == '*' && sql[i+1] == '/' {
inMultiLineComment = false
i++ // 跳过 '/'
continue
}
// 不是注释内容时,将字符添加到结果中
if !inSingleLineComment && !inMultiLineComment {
result.WriteByte(sql[i])
}
}
// 去除多余的空白行
lines := strings.Split(result.String(), "\n")
var cleanedLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" {
cleanedLines = append(cleanedLines, trimmed)
}
}
return strings.Join(cleanedLines, "\n")
}
// splitSQLStatements 更安全地分割 SQL 语句
func splitSQLStatements(sql string) []string {
statements := strings.Split(sql, ";")
var results []string
for _, stmt := range statements {
trimmed := strings.TrimSpace(stmt)
if trimmed != "" {
results = append(results, trimmed)
}
}
return results
}

32
initialize/mobile.go Normal file
View File

@ -0,0 +1,32 @@
package initialize
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Mobile(ctx *svc.ServiceContext) {
logger.Debug("Mobile config initialization")
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "mobile")
if err != nil {
panic(err)
}
var cfg config.MobileConfig
var mobileConfig auth.MobileAuthConfig
if err := mobileConfig.Unmarshal(method.Config); err != nil {
panic(fmt.Sprintf("failed to unmarshal mobile auth config: %v", err.Error()))
}
tool.DeepCopy(&cfg, mobileConfig)
cfg.Enable = *method.Enabled
value, _ := json.Marshal(mobileConfig.PlatformConfig)
cfg.PlatformConfig = string(value)
ctx.Config.Mobile = cfg
}

10
initialize/mysql.go Normal file
View File

@ -0,0 +1,10 @@
package initialize
import (
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
func Mysql(ctx *svc.ServiceContext) {
migrate.Migrate(ctx)
}

51
initialize/node.go Normal file
View File

@ -0,0 +1,51 @@
package initialize
import (
"context"
"encoding/json"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/nodeMultiplier"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Node(ctx *svc.ServiceContext) {
logger.Debug("Node config initialization")
configs, err := ctx.SystemModel.GetNodeConfig(context.Background())
if err != nil {
panic(err)
}
var nodeConfig config.NodeConfig
tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig)
ctx.Config.Node = nodeConfig
// Manager initialization
if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 {
if err := ctx.DB.Model(&system.System{}).Create(&system.System{
Key: "NodeMultiplierConfig",
Value: "[]",
Type: "string",
Desc: "Node Multiplier Config",
Category: "server",
}).Error; err != nil {
logger.Errorf("Create Node Multiplier Config Error: %s", err.Error())
}
return
}
nodeMultiplierData, err := ctx.SystemModel.FindNodeMultiplierConfig(context.Background())
if err != nil {
logger.Error("Get Node Multiplier Config Error: ", logger.Field("error", err.Error()))
return
}
var periods []nodeMultiplier.TimePeriod
if err := json.Unmarshal([]byte(nodeMultiplierData.Value), &periods); err != nil {
logger.Error("Unmarshal Node Multiplier Config Error: ", logger.Field("error", err.Error()), logger.Field("value", nodeMultiplierData.Value))
}
ctx.NodeMultiplierManager = nodeMultiplier.NewNodeMultiplierManager(periods)
}

11
initialize/oauth.go Normal file
View File

@ -0,0 +1,11 @@
package initialize
import (
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
func OAuth(svc *svc.ServiceContext) {
logger.Debug("OAuth config initialization")
}

23
initialize/register.go Normal file
View File

@ -0,0 +1,23 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Register(ctx *svc.ServiceContext) {
logger.Debug("Register config initialization")
configs, err := ctx.SystemModel.GetRegisterConfig(context.Background())
if err != nil {
logger.Errorf("[Init Register Config] Get Register Config Error: %s", err.Error())
return
}
var registerConfig config.RegisterConfig
tool.SystemConfigSliceReflectToStruct(configs, &registerConfig)
ctx.Config.Register = registerConfig
}

22
initialize/site.go Normal file
View File

@ -0,0 +1,22 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Site(ctx *svc.ServiceContext) {
logger.Debug("initialize site config")
configs, err := ctx.SystemModel.GetSiteConfig(context.Background())
if err != nil {
panic(err)
}
var siteConfig config.SiteConfig
tool.SystemConfigSliceReflectToStruct(configs, &siteConfig)
ctx.Config.Site = siteConfig
}

57
initialize/statistics.go Normal file
View File

@ -0,0 +1,57 @@
package initialize
import (
"context"
"time"
"github.com/perfect-panel/ppanel-server/internal/model/cache"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
func TrafficDataToRedis(svcCtx *svc.ServiceContext) {
ctx := context.Background()
// 统计昨天的节点流量数据排行榜前10
nodeData, err := svcCtx.TrafficLogModel.TopServersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
if err != nil {
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
}
var nodeCacheData []cache.NodeTodayTrafficRank
for _, node := range nodeData {
serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId)
if err != nil {
logger.Errorw("查询节点信息失败", logger.Field("error", err.Error()))
continue
}
nodeCacheData = append(nodeCacheData, cache.NodeTodayTrafficRank{
ID: node.ServerId,
Name: serverInfo.Name,
Upload: node.Upload,
Download: node.Download,
Total: node.Upload + node.Download,
})
}
// 写入缓存
if err = svcCtx.NodeCache.UpdateYesterdayNodeTotalTrafficRank(ctx, nodeCacheData); err != nil {
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
}
// 统计昨天的用户流量数据排行榜前10
userData, err := svcCtx.TrafficLogModel.TopUsersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
if err != nil {
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
}
var userCacheData []cache.UserTodayTrafficRank
for _, user := range userData {
userCacheData = append(userCacheData, cache.UserTodayTrafficRank{
SID: user.SubscribeId,
Upload: user.Upload,
Download: user.Download,
Total: user.Upload + user.Download,
})
}
// 写入缓存
if err = svcCtx.NodeCache.UpdateYesterdayUserTotalTrafficRank(ctx, userCacheData); err != nil {
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
}
logger.Infow("初始化昨天的流量数据到缓存成功")
}

24
initialize/subscribe.go Normal file
View File

@ -0,0 +1,24 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Subscribe(svc *svc.ServiceContext) {
logger.Debug("Subscribe config initialization")
configs, err := svc.SystemModel.GetSubscribeConfig(context.Background())
if err != nil {
logger.Error("[Init Subscribe Config] Get Subscribe Config Error: ", logger.Field("error", err.Error()))
return
}
var subscribeConfig config.SubscribeConfig
tool.SystemConfigSliceReflectToStruct(configs, &subscribeConfig)
svc.Config.Subscribe = subscribeConfig
}

82
initialize/telegram.go Normal file
View File

@ -0,0 +1,82 @@
package initialize
import (
"context"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/logic/telegram"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Telegram(svc *svc.ServiceContext) {
method, err := svc.AuthModel.FindOneByMethod(context.Background(), "telegram")
if err != nil {
logger.Errorf("[Init Telegram Config] Get Telegram Config Error: %s", err.Error())
return
}
var tg config.Telegram
tgConfig := new(auth.TelegramAuthConfig)
if err = tgConfig.Unmarshal(method.Config); err != nil {
logger.Errorf("[Init Telegram Config] Unmarshal Telegram Config Error: %s", err.Error())
return
}
if tgConfig.BotToken == "" {
logger.Debug("[Init Telegram Config] Telegram Token is empty")
return
}
bot, err := tgbotapi.NewBotAPI(tg.BotToken)
if err != nil {
logger.Error("[Init Telegram Config] New Bot API Error: ", logger.Field("error", err.Error()))
return
}
if tgConfig.WebHookDomain == "" || svc.Config.Debug {
// set Long Polling mode
updateConfig := tgbotapi.NewUpdate(0)
updateConfig.Timeout = 60
updates := bot.GetUpdatesChan(updateConfig)
go func() {
for update := range updates {
if update.Message != nil {
ctx := context.Background()
l := telegram.NewTelegramLogic(ctx, svc)
l.TelegramLogic(&update)
}
}
}()
} else {
wh, err := tgbotapi.NewWebhook(fmt.Sprintf("%s/v1/telegram/webhook?secret=%s", tgConfig.WebHookDomain, tool.Md5Encode(tgConfig.BotToken, false)))
if err != nil {
logger.Errorf("[Init Telegram Config] New Webhook Error: %s", err.Error())
return
}
_, err = bot.Request(wh)
if err != nil {
logger.Errorf("[Init Telegram Config] Request Webhook Error: %s", err.Error())
return
}
}
user, err := bot.GetMe()
if err != nil {
logger.Error("[Init Telegram Config] Get Bot Info Error: ", logger.Field("error", err.Error()))
return
}
svc.Config.Telegram.BotID = user.ID
svc.Config.Telegram.BotName = user.UserName
svc.Config.Telegram.EnableNotify = tg.EnableNotify
svc.Config.Telegram.WebHookDomain = tg.WebHookDomain
svc.TelegramBot = bot
logger.Info("[Init Telegram Config] Webhook set success")
}

View File

@ -0,0 +1,469 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 头部内容 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PPanel - Application Initialization</title>
<!-- 引入 Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Tailwind CSS 配置 -->
<script>
tailwind.config = {
darkMode: ["class"],
theme: {
container: { center: true, padding: "2rem" },
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" },
},
borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" },
},
},
}
</script>
<!-- 自定义样式 -->
<style>
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--destructive: 346.8 77.2% 49.8%;
--destructive-foreground: 355.7 100% 97.3%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
</style>
</head>
<body class="min-h-screen font-sans antialiased">
<div id="appContainer" class="container mx-auto p-4 flex flex-col items-center min-h-screen">
<div class="w-full max-w-2xl space-y-6">
<!-- 设置卡片 -->
<div id="setupCard" class="p-6 space-y-6 bg-white rounded-lg border">
<div class="flex justify-between items-center">
<h1 id="title" class="text-3xl font-bold">Welcome to PPanel Setup</h1>
<button onclick="switchLanguage()" id="langSwitch" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">中文</button>
</div>
<p id="description" class="text-gray-600 !mt-0">Let's get your PPanel application up and running. Please provide the necessary information below.</p>
<form id="setupForm" class="space-y-6" onsubmit="handleSubmit(event)">
<!-- 管理员详情 -->
<div class="space-y-4">
<h2 id="adminInfoTitle" class="font-bold text-xl">Administrator Details</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="adminEmail" id="labelAdminEmail" class="text-sm font-medium">Administrator Email</label>
<input type="email" id="adminEmail" name="adminEmail" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator email" data-placeholder-en="Enter administrator email" data-placeholder-zh="请输入管理员邮箱" oninput="validateInput(this)">
<span id="adminEmailError" class="text-red-500 text-sm mt-1 hidden">Invalid email address.</span>
</div>
<div>
<label for="adminPassword" id="labelAdminPassword" class="text-sm font-medium">Administrator Password</label>
<input type="password" id="adminPassword" name="adminPassword" required minlength="6" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator password" data-placeholder-en="Enter administrator password" data-placeholder-zh="请输入管理员密码" oninput="validateInput(this)">
<span id="adminPasswordError" class="text-red-500 text-sm mt-1 hidden">Password must be at least 6 characters.</span>
</div>
</div>
</div>
<!-- MySQL 数据库设置 -->
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 id="mysqlInfoTitle" class="font-bold text-xl">MySQL Database Setup</h2>
<button type="button" onclick="testConnection('mysql')" id="testMySQLButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test MySQL Connection</button>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="mysqlHost" id="labelMysqlHost" class="text-sm font-medium">Database Host</label>
<input type="text" id="mysqlHost" name="mysqlHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="localhost" placeholder="Enter database host" data-placeholder-en="Enter database host" data-placeholder-zh="请输入数据库主机">
<span id="mysqlHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
</div>
<div>
<label for="mysqlPort" id="labelMysqlPort" class="text-sm font-medium">Database Port</label>
<input type="number" id="mysqlPort" name="mysqlPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="3306" placeholder="Enter database port" data-placeholder-en="Enter database port" data-placeholder-zh="请输入数据库端口">
<span id="mysqlPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
</div>
<div>
<label for="mysqlUser" id="labelMysqlUser" class="text-sm font-medium">Database User</label>
<input type="text" id="mysqlUser" name="mysqlUser" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="root" placeholder="Enter database user" data-placeholder-en="Enter database user" data-placeholder-zh="请输入数据库用户名">
<span id="mysqlUserError" class="text-red-500 text-sm mt-1 hidden">Please enter a username.</span>
</div>
<div>
<label for="mysqlPassword" id="labelMysqlPassword" class="text-sm font-medium">Database Password</label>
<input type="password" id="mysqlPassword" name="mysqlPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter database password" data-placeholder-en="Enter database password" data-placeholder-zh="请输入数据库密码">
</div>
<div>
<label for="mysqlDatabase" id="labelMysqlDatabase" class="text-sm font-medium">Database Name</label>
<input type="text" id="mysqlDatabase" name="mysqlDatabase" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="ppanel_db" placeholder="Enter database name" data-placeholder-en="Enter database name" data-placeholder-zh="请输入数据库名称">
<span id="mysqlDatabaseError" class="text-red-500 text-sm mt-1 hidden">Please enter a database name.</span>
</div>
</div>
</div>
<!-- Redis 缓存设置 -->
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 id="redisInfoTitle" class="font-bold text-xl">Redis Cache Setup</h2>
<button type="button" onclick="testConnection('redis')" id="testRedisButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test Redis Connection</button>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="redisHost" id="labelRedisHost" class="text-sm font-medium">Redis Host</label>
<input type="text" id="redisHost" name="redisHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="127.0.0.1" placeholder="Enter Redis host" data-placeholder-en="Enter Redis host" data-placeholder-zh="请输入 Redis 主机">
<span id="redisHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
</div>
<div>
<label for="redisPort" id="labelRedisPort" class="text-sm font-medium">Redis Port</label>
<input type="number" id="redisPort" name="redisPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="6379" placeholder="Enter Redis port" data-placeholder-en="Enter Redis port" data-placeholder-zh="请输入 Redis 端口">
<span id="redisPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
</div>
<div>
<label for="redisPassword" id="labelRedisPassword" class="text-sm font-medium">Redis Password</label>
<input type="password" id="redisPassword" name="redisPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter Redis password (optional)" data-placeholder-en="Enter Redis password (optional)" data-placeholder-zh="请输入 Redis 密码(可选)">
</div>
</div>
</div>
<!-- 提交按钮 -->
<button type="submit" id="submitButton" disabled class="w-full h-10 px-4 py-2 bg-primary text-white rounded-md opacity-50 cursor-not-allowed">Start Initialization</button>
</form>
</div>
</div>
</div>
<!-- 安装中卡片 -->
<div id="installingCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="installingTitle" class="text-3xl font-bold">Installing...</h1>
<p id="installingMessage">Please wait while we initialize your application.</p>
<div class="flex justify-center">
<svg class="animate-spin h-10 w-10 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
</div>
</div>
</div>
<!-- 安装完成卡片 -->
<div id="installationCompleteCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="installationCompleteTitle" class="text-3xl font-bold">Installation Complete</h1>
<p id="installationCompleteMessage">You can now close this page.</p>
</div>
</div>
<!-- 已初始化页面 -->
<div id="alreadyInitializedCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="alreadyInitializedTitle" class="text-3xl font-bold">Already Initialized</h1>
<p id="alreadyInitializedMessage">Your PPanel application has already been initialized.</p>
</div>
</div>
<!-- 对话框 -->
<div id="dialog" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg p-6 w-96">
<h2 id="dialogTitle" class="text-xl font-bold mb-4"></h2>
<p id="dialogMessage" class="mb-4"></p>
<div class="flex justify-end">
<button onclick="closeDialog()" class="h-10 px-4 py-2 bg-primary text-white rounded-md">Close</button>
</div>
</div>
</div>
<!-- JavaScript -->
<script>
let currentLang = 'en';
const translations = {
en: {
// 英文翻译内容
title: 'Welcome to PPanel Setup',
description: "Let's get your PPanel application up and running. Please provide the necessary information below.",
adminInfoTitle: 'Administrator Details',
mysqlInfoTitle: 'MySQL Database Setup',
redisInfoTitle: 'Redis Cache Setup',
adminEmail: 'Administrator Email',
adminPassword: 'Administrator Password',
mysqlHost: 'Database Host',
mysqlPort: 'Database Port',
mysqlUser: 'Database User',
mysqlPassword: 'Database Password',
mysqlDatabase: 'Database Name',
redisHost: 'Redis Host',
redisPort: 'Redis Port',
redisPassword: 'Redis Password',
submit: 'Start Initialization',
switchLang: '中文',
testMySQLConnection: 'Test MySQL Connection',
testRedisConnection: 'Test Redis Connection',
connectionSuccess: 'Connection Successful',
connectionError: 'Connection Failed',
initializationError: 'Initialization Error',
initializationErrorMessage: 'An error occurred during initialization. Please try again.',
installingTitle: 'Installing...',
installingMessage: 'Please wait while we initialize your application.',
installationCompleteTitle: 'Installation Complete',
installationCompleteMessage: 'You can now close this page.',
proceedButton: 'Go to Application',
mysqlConnectionSuccess: 'Successfully connected to the MySQL database.',
mysqlConnectionError: 'Failed to connect to the MySQL database. Please check your settings.',
redisConnectionSuccess: 'Successfully connected to the Redis server.',
redisConnectionError: 'Failed to connect to the Redis server. Please check your settings.',
alreadyInitializedTitle: 'Already Initialized',
alreadyInitializedMessage: 'Your PPanel application has already been initialized.',
invalidEmail: 'Invalid email address.',
invalidPassword: 'Password must be at least 6 characters.',
},
zh: {
// 中文翻译内容
title: '欢迎使用 PPanel 设置向导',
description: '让我们一起启动您的 PPanel 应用程序。请提供以下必要信息。',
adminInfoTitle: '管理员详情',
mysqlInfoTitle: 'MySQL 数据库设置',
redisInfoTitle: 'Redis 缓存设置',
adminEmail: '管理员邮箱',
adminPassword: '管理员密码',
mysqlHost: '数据库主机',
mysqlPort: '数据库端口',
mysqlUser: '数据库用户',
mysqlPassword: '数据库密码',
mysqlDatabase: '数据库名称',
redisHost: 'Redis 主机',
redisPort: 'Redis 端口',
redisPassword: 'Redis 密码',
submit: '开始初始化',
switchLang: 'English',
testMySQLConnection: '测试 MySQL 连接',
testRedisConnection: '测试 Redis 连接',
connectionSuccess: '连接成功',
connectionError: '连接失败',
initializationError: '初始化错误',
initializationErrorMessage: '初始化过程中发生错误。请重试。',
installingTitle: '正在安装...',
installingMessage: '请稍候,我们正在初始化您的应用程序。',
installationCompleteTitle: '安装完成',
installationCompleteMessage: '您现在可以关闭此页面。',
proceedButton: '前往应用程序',
mysqlConnectionSuccess: '成功连接到 MySQL 数据库。',
mysqlConnectionError: '无法连接到 MySQL 数据库。请检查您的设置。',
redisConnectionSuccess: '成功连接到 Redis 服务器。',
redisConnectionError: '无法连接到 Redis 服务器。请检查您的设置。',
alreadyInitializedTitle: '已初始化',
alreadyInitializedMessage: '您的 PPanel 应用程序已被初始化。',
invalidEmail: '无效的邮箱地址。',
invalidPassword: '密码至少需要6个字符。',
}
};
function switchLanguage() {
currentLang = currentLang === 'en' ? 'zh' : 'en';
updateTexts();
}
function updateTexts() {
const t = translations[currentLang];
document.getElementById('title').textContent = t.title;
document.getElementById('description').textContent = t.description;
document.getElementById('adminInfoTitle').textContent = t.adminInfoTitle;
document.getElementById('mysqlInfoTitle').textContent = t.mysqlInfoTitle;
document.getElementById('redisInfoTitle').textContent = t.redisInfoTitle;
document.getElementById('labelAdminEmail').textContent = t.adminEmail;
document.getElementById('labelAdminPassword').textContent = t.adminPassword;
document.getElementById('labelMysqlHost').textContent = t.mysqlHost;
document.getElementById('labelMysqlPort').textContent = t.mysqlPort;
document.getElementById('labelMysqlUser').textContent = t.mysqlUser;
document.getElementById('labelMysqlPassword').textContent = t.mysqlPassword;
document.getElementById('labelMysqlDatabase').textContent = t.mysqlDatabase;
document.getElementById('labelRedisHost').textContent = t.redisHost;
document.getElementById('labelRedisPort').textContent = t.redisPort;
document.getElementById('labelRedisPassword').textContent = t.redisPassword;
document.getElementById('submitButton').textContent = t.submit;
document.getElementById('langSwitch').textContent = t.switchLang;
document.getElementById('testMySQLButton').textContent = t.testMySQLConnection;
document.getElementById('testRedisButton').textContent = t.testRedisConnection;
document.getElementById('installingTitle').textContent = t.installingTitle;
document.getElementById('installingMessage').textContent = t.installingMessage;
document.getElementById('installationCompleteTitle').textContent = t.installationCompleteTitle;
document.getElementById('installationCompleteMessage').textContent = t.installationCompleteMessage;
document.getElementById('alreadyInitializedTitle').textContent = t.alreadyInitializedTitle;
document.getElementById('alreadyInitializedMessage').textContent = t.alreadyInitializedMessage;
// 更新 placeholder 文本
const inputs = document.querySelectorAll('.input-field');
inputs.forEach((input) => {
const placeholder = input.getAttribute(`data-placeholder-${currentLang}`);
if (placeholder) {
input.setAttribute('placeholder', placeholder);
}
});
}
function checkInitialization() {
const isInitialized = false; // 需要从服务器获取实际状态
if (isInitialized) {
document.getElementById('appContainer').classList.add('hidden');
document.getElementById('alreadyInitializedCard').classList.remove('hidden');
}
}
function testConnection(type) {
const t = translations[currentLang];
let data = {};
let url = '';
if (type === 'mysql') {
data = {
host: document.getElementById('mysqlHost').value,
port: document.getElementById('mysqlPort').value,
user: document.getElementById('mysqlUser').value,
password: document.getElementById('mysqlPassword').value,
database: document.getElementById('mysqlDatabase').value,
};
url = '/init/mysql/test';
} else if (type === 'redis') {
data = {
host: document.getElementById('redisHost').value,
port: document.getElementById('redisPort').value,
password: document.getElementById('redisPassword').value,
};
url = '/init/redis/test';
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
console.log(result);
if (result.code === 200 && result.status) {
showDialog(t.connectionSuccess, result.msg || (type === 'mysql' ? t.mysqlConnectionSuccess : t.redisConnectionSuccess));
} else {
showDialog(t.connectionError, result.msg || (type === 'mysql' ? t.mysqlConnectionError : t.redisConnectionError));
}
})
.catch(error => {
console.error('Error:', error);
showDialog(t.connectionError, t.initializationErrorMessage);
});
}
function handleSubmit(event) {
event.preventDefault();
const form = document.getElementById('setupForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const t = translations[currentLang];
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// 显示安装中界面
document.getElementById('setupCard').classList.add('hidden');
document.getElementById('installingCard').classList.remove('hidden');
// 发送初始化请求
fetch('/init/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
document.getElementById('installingCard').classList.add('hidden');
if (result.code === 200 && result.status) {
document.getElementById('installationCompleteCard').classList.remove('hidden');
} else {
showDialog(t.initializationError, result.msg || t.initializationErrorMessage);
document.getElementById('setupCard').classList.remove('hidden');
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('installingCard').classList.add('hidden');
showDialog(t.initializationError, t.initializationErrorMessage);
document.getElementById('setupCard').classList.remove('hidden');
});
}
function showDialog(title, message) {
document.getElementById('dialogTitle').textContent = title;
document.getElementById('dialogMessage').textContent = message;
document.getElementById('dialog').classList.remove('hidden');
}
function closeDialog() {
document.getElementById('dialog').classList.add('hidden');
}
// 实时表单校验
function validateInput(input) {
const errorSpan = document.getElementById(input.id + 'Error');
if (input.type === 'email') {
// 邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input.value)) {
errorSpan.textContent = translations[currentLang].invalidEmail;
errorSpan.classList.remove('hidden');
input.setCustomValidity(translations[currentLang].invalidEmail);
} else {
input.setCustomValidity('');
errorSpan.classList.add('hidden');
}
} else if (input.validity.valid) {
errorSpan.classList.add('hidden');
input.setCustomValidity('');
} else {
errorSpan.classList.remove('hidden');
}
updateSubmitButtonState();
}
function updateSubmitButtonState() {
const form = document.getElementById('setupForm');
const submitButton = document.getElementById('submitButton');
if (form.checkValidity()) {
submitButton.disabled = false;
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
} else {
submitButton.disabled = true;
submitButton.classList.add('opacity-50', 'cursor-not-allowed');
}
}
// 添加事件监听器
document.addEventListener('DOMContentLoaded', () => {
const inputs = document.querySelectorAll('.input-field');
inputs.forEach((input) => {
input.addEventListener('input', () => validateInput(input));
});
updateSubmitButtonState();
updateTexts();
checkInitialization();
});
</script>
</body>
</html>

48
initialize/verify.go Normal file
View File

@ -0,0 +1,48 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
type verifyConfig struct {
TurnstileSiteKey string
TurnstileSecret string
EnableLoginVerify bool
EnableRegisterVerify bool
EnableResetPasswordVerify bool
}
func Verify(svc *svc.ServiceContext) {
logger.Debug("Verify config initialization")
configs, err := svc.SystemModel.GetVerifyConfig(context.Background())
if err != nil {
logger.Error("[Init Verify Config] Get Verify Config Error: ", logger.Field("error", err.Error()))
return
}
var verify verifyConfig
tool.SystemConfigSliceReflectToStruct(configs, &verify)
svc.Config.Verify = config.Verify{
TurnstileSiteKey: verify.TurnstileSiteKey,
TurnstileSecret: verify.TurnstileSecret,
LoginVerify: verify.EnableLoginVerify,
RegisterVerify: verify.EnableRegisterVerify,
ResetPasswordVerify: verify.EnableResetPasswordVerify,
}
logger.Debug("Verify code config initialization")
var verifyCodeConfig config.VerifyCode
cfg, err := svc.SystemModel.GetVerifyCodeConfig(context.Background())
if err != nil {
logger.Errorf("[Init Verify Config] Get Verify Code Config Error: %s", err.Error())
return
}
tool.SystemConfigSliceReflectToStruct(cfg, &verifyCodeConfig)
svc.Config.VerifyCode = verifyCodeConfig
}

127
initialize/version.go Normal file
View File

@ -0,0 +1,127 @@
package initialize
import (
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
"github.com/perfect-panel/ppanel-server/initialize/migrate/patch"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func VerifyVersion(ctx *svc.ServiceContext) {
var configVersion system.System
err := ctx.DB.Transaction(func(db *gorm.DB) error {
db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").First(&configVersion)
if configVersion.Value != constant.Version {
// Version eg: 1.0.0(10000)
current := tool.ExtractVersionNumber(constant.Version)
sqlVersion := tool.ExtractVersionNumber(configVersion.Value)
logger.Infof("Verify System Version, current version: %d, datebase version: %d", current, sqlVersion)
if current > sqlVersion {
// Migrate to Milestone Version
//
// Migrate SQL to 0.1.7(01703)
if sqlVersion < 1705 {
if err := migrate01701(db, sqlVersion); err != nil {
return err
}
}
// 重新执行2000版本的迁移
if sqlVersion == 2002 {
sqlVersion = 2000
}
// Migrate SQL to 0.2.0(02000)
if sqlVersion < 2009 {
if err := migrate02000(db, sqlVersion); err != nil {
return err
}
}
// Migrate SQL to 0.3.0(03000)
if sqlVersion < 3002 {
if err := migrate03000(db, sqlVersion); err != nil {
return err
}
}
}
}
return nil
})
if err != nil {
panic("update system version error:" + err.Error())
}
}
func migrate01701(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
1200: patch.Migrate01200,
1201: patch.Migrate01201,
1202: patch.Migrate01202,
1203: patch.Migrate01203,
1204: patch.Migrate01204,
1205: patch.Migrate01205,
1301: patch.Migrate01301,
1602: patch.Migrate01602,
1701: patch.Migrate01701,
1702: patch.Migrate01702,
1703: patch.Migrate01703,
1704: patch.Migrate01704,
1705: patch.Migrate01705,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}
func migrate02000(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
2000: patch.Migrate02000,
2001: patch.Migrate02001,
2002: patch.Migrate02002,
2003: patch.Migrate02003,
2007: patch.Migrate02007,
2008: patch.Migrate02008,
2009: patch.Migrate02009,
2010: patch.Migrate02010,
2011: patch.Migrate02011,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}
func migrate03000(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
3001: patch.Migrate03001,
3002: patch.Migrate03002,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}

View File

@ -0,0 +1,78 @@
package config
// CurrencyConfigKey Currency Config Key
const CurrencyConfigKey = "system:currency_config"
// SmsConfigKey Mobile Config Key
const SmsConfigKey = "system:sms_config"
// SiteConfigKey Site Config Key
const SiteConfigKey = "system:site_config"
// SubscribeConfigKey Subscribe Config Key
const SubscribeConfigKey = "system:subscribe_config"
// ApplicationKey Application Key
const ApplicationKey = "system:application"
// RegisterConfigKey Register Config Key
const RegisterConfigKey = "system:register_config"
// VerifyConfigKey Verify Config Key
const VerifyConfigKey = "system:verify_config"
// EmailSmtpConfigKey Email Smtp Config Key
const EmailSmtpConfigKey = "system:email_smtp_config"
// NodeConfigKey Node Config Key
const NodeConfigKey = "system:node_config"
// InviteConfigKey Invite Config Key
const InviteConfigKey = "system:invite_config"
// TelegramConfigKey Telegram Config Key
const TelegramConfigKey = "system:telegram_config"
// TosConfigKey Tos配置
const TosConfigKey = "system:tos_config"
// VerifyCodeConfigKey Verify Code Config Key
const VerifyCodeConfigKey = "system:verify_code_config"
// SessionIdKey cache session key
const SessionIdKey = "auth:session_id"
// GlobalConfigKey Global Config Key
const GlobalConfigKey = "system:global_config"
// AuthCodeCacheKey Register Code Cache Key
const AuthCodeCacheKey = "auth:verify:email"
// AuthCodeTelephoneCacheKey Register Code Cache Key
const AuthCodeTelephoneCacheKey = "auth:verify:telephone"
// ServerUserListCacheKey Server User List Cache Key
const ServerUserListCacheKey = "server:user_list:id:"
// ServerConfigCacheKey Server Config Cache Key
const ServerConfigCacheKey = "server:config:id:"
// CommonStat Cache Key
const CommonStatCacheKey = "common:stat"
// ServerStatusCacheKey Server Status Cache Key
const ServerStatusCacheKey = "server:status:id:"
// ServerCountCacheKey Server Count Cache Key
const ServerCountCacheKey = "server:count"
// UserBindTelegramCacheKey User Bind Telegram Cache Key
const UserBindTelegramCacheKey = "user:bind:telegram:code:"
const CacheSmsCount = "cache:sms:count"
// SendIntervalKeyPrefix Auth Code Send Interval Key Prefix
const SendIntervalKeyPrefix = "send:interval:"
// SendCountLimitKeyPrefix Send Count Limit Key Prefix eg. send:limit:register:email:xxx@ppanel.dev
const SendCountLimitKeyPrefix = "send:limit:"

146
internal/config/config.go Normal file
View File

@ -0,0 +1,146 @@
package config
import (
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/orm"
)
type Config struct {
Model string `yaml:"Model" default:"prod"`
Host string `yaml:"Host" default:"0.0.0.0"`
Port int `yaml:"Port" default:"8080"`
Debug bool `yaml:"Debug" default:"false"`
TLS TLS `yaml:"TLS"`
JwtAuth JwtAuth `yaml:"JwtAuth"`
Logger logger.LogConf `yaml:"Logger"`
MySQL orm.Config `yaml:"MySQL"`
Redis RedisConfig `yaml:"Redis"`
Site SiteConfig `yaml:"Site"`
Node NodeConfig `yaml:"Node"`
Mobile MobileConfig `yaml:"Mobile"`
Email EmailConfig `yaml:"Email"`
Verify Verify `yaml:"Verify"`
VerifyCode VerifyCode `yaml:"VerifyCode"`
Register RegisterConfig `yaml:"Register"`
Subscribe SubscribeConfig `yaml:"Subscribe"`
Invite InviteConfig `yaml:"Invite"`
Telegram Telegram `yaml:"Telegram"`
Administrator struct {
Email string `yaml:"Email" default:"admin@ppanel.dev"`
Password string `yaml:"Password" default:"password"`
} `yaml:"Administrator"`
}
type RedisConfig struct {
Host string `yaml:"Host" default:"localhost:6379"`
Pass string `yaml:"Pass" default:""`
DB int `yaml:"DB" default:"0"`
}
type JwtAuth struct {
AccessSecret string `yaml:"AccessSecret"`
AccessExpire int64 `yaml:"AccessExpire" default:"604800"`
}
type Verify struct {
TurnstileSiteKey string `yaml:"TurnstileSiteKey" default:""`
TurnstileSecret string `yaml:"TurnstileSecret" default:""`
LoginVerify bool `yaml:"LoginVerify" default:"false"`
RegisterVerify bool `yaml:"RegisterVerify" default:"false"`
ResetPasswordVerify bool `yaml:"ResetPasswordVerify" default:"false"`
}
type SubscribeConfig struct {
SingleModel bool `yaml:"SingleModel" default:"false"`
SubscribePath string `yaml:"SubscribePath" default:"/api/subscribe"`
SubscribeDomain string `yaml:"SubscribeDomain" default:""`
PanDomain bool `yaml:"PanDomain" default:"false"`
}
type RegisterConfig struct {
StopRegister bool `yaml:"StopRegister" default:"false"`
EnableTrial bool `yaml:"EnableTrial" default:"false"`
TrialSubscribe int64 `yaml:"TrialSubscribe" default:"0"`
TrialTime int64 `yaml:"TrialTime" default:"0"`
TrialTimeUnit string `yaml:"TrialTimeUnit" default:""`
IpRegisterLimit int64 `yaml:"IpRegisterLimit" default:"0"`
IpRegisterLimitDuration int64 `yaml:"IpRegisterLimitDuration" default:"0"`
EnableIpRegisterLimit bool `yaml:"EnableIpRegisterLimit" default:"false"`
}
type EmailConfig struct {
Enable bool `yaml:"Enable" default:"true"`
Platform string `yaml:"platform"`
PlatformConfig string `yaml:"platform_config"`
EnableVerify bool `yaml:"enable_verify"`
EnableNotify bool `yaml:"enable_notify"`
EnableDomainSuffix bool `yaml:"enable_domain_suffix"`
DomainSuffixList string `yaml:"domain_suffix_list"`
VerifyEmailTemplate string `yaml:"verify_email_template"`
ExpirationEmailTemplate string `yaml:"expiration_email_template"`
MaintenanceEmailTemplate string `yaml:"maintenance_email_template"`
TrafficExceedEmailTemplate string `yaml:"traffic_exceed_email_template"`
}
type MobileConfig struct {
Enable bool `yaml:"Enable" default:"true"`
Platform string `yaml:"platform"`
PlatformConfig string `yaml:"platform_config"`
EnableVerify bool `yaml:"enable_verify"`
EnableWhitelist bool `yaml:"enable_whitelist"`
Whitelist []string `yaml:"whitelist"`
}
type SiteConfig struct {
Host string `yaml:"Host" default:""`
SiteName string `yaml:"SiteName" default:""`
SiteDesc string `yaml:"SiteDesc" default:""`
SiteLogo string `yaml:"SiteLogo" default:""`
Keywords string `yaml:"Keywords" default:""`
CustomHTML string `yaml:"CustomHTML" default:""`
CustomData string `yaml:"CustomData" default:""`
}
type NodeConfig struct {
NodeSecret string `yaml:"NodeSecret" default:""`
NodePullInterval int64 `yaml:"NodePullInterval" default:"60"`
NodePushInterval int64 `yaml:"NodePushInterval" default:"60"`
}
type File struct {
Host string `yaml:"Host" default:"0.0.0.0"`
Port int `yaml:"Port" default:"8080"`
TLS TLS `yaml:"TLS"`
Debug bool `yaml:"Debug" default:"true"`
JwtAuth JwtAuth `yaml:"JwtAuth"`
Logger logger.LogConf `yaml:"Logger"`
MySQL orm.Config `yaml:"MySQL"`
Redis RedisConfig `yaml:"Redis"`
}
type InviteConfig struct {
ForcedInvite bool `yaml:"ForcedInvite" default:"false"`
ReferralPercentage int64 `yaml:"ReferralPercentage" default:"0"`
OnlyFirstPurchase bool `yaml:"OnlyFirstPurchase" default:"false"`
}
type Telegram struct {
Enable bool `yaml:"Enable" default:"false"`
BotID int64 `yaml:"BotID" default:""`
BotName string `yaml:"BotName" default:""`
BotToken string `yaml:"BotToken" default:""`
EnableNotify bool `yaml:"EnableNotify" default:"false"`
WebHookDomain string `yaml:"WebHookDomain" default:""`
}
type TLS struct {
Enable bool `yaml:"Enable" default:"false"`
CertFile string `yaml:"CertFile" default:""`
KeyFile string `yaml:"KeyFile" default:""`
}
type VerifyCode struct {
ExpireTime int64 `yaml:"ExpireTime" default:"300"`
Limit int64 `yaml:"Limit" default:"15"`
Interval int64 `yaml:"Interval" default:"60"`
}

View File

@ -0,0 +1,5 @@
package config
import "github.com/perfect-panel/ppanel-server/pkg/constant"
const Version = constant.Version

View File

@ -0,0 +1,10 @@
package config
type Protocol string
const (
Shadowsocks Protocol = "shadowsocks"
Trojan Protocol = "trojan"
Vmess Protocol = "vmess"
Vless Protocol = "vless"
)

View File

@ -0,0 +1,27 @@
package ads
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/logic/admin/ads"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/result"
)
// Create Ads
func CreateAdsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.CreateAdsRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
ctx := c.Request.Context()
l := ads.NewCreateAdsLogic(ctx, svcCtx)
err := l.CreateAds(&req)
result.HttpResult(c, nil, err)
}
}

View File

@ -0,0 +1,27 @@
package ads
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/logic/admin/ads"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/result"
)
// Delete Ads
func DeleteAdsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.DeleteAdsRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
ctx := c.Request.Context()
l := ads.NewDeleteAdsLogic(ctx, svcCtx)
err := l.DeleteAds(&req)
result.HttpResult(c, nil, err)
}
}

View File

@ -0,0 +1,27 @@
package ads
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/ppanel-server/internal/logic/admin/ads"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/result"
)
// Get Ads Detail
func GetAdsDetailHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.GetAdsDetailRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
ctx := c.Request.Context()
l := ads.NewGetAdsDetailLogic(ctx, svcCtx)
resp, err := l.GetAdsDetail(&req)
result.HttpResult(c, resp, err)
}
}

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