init: 1.0.0

This commit is contained in:
Chang lue Tsen
2025-04-25 12:08:29 +09:00
commit 8addcc584b
1031 changed files with 76472 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
package email
import "github.com/perfect-panel/ppanel-server/internal/types"
type Platform int
const (
SMTP Platform = iota
unsupported
)
var platformNames = map[string]Platform{
"smtp": SMTP,
"unsupported": unsupported,
}
func (p Platform) String() string {
for k, v := range platformNames {
if v == p {
return k
}
}
return "unsupported"
}
func parsePlatform(s string) Platform {
if p, ok := platformNames[s]; ok {
return p
}
return unsupported
}
func GetSupportedPlatforms() []types.PlatformInfo {
return []types.PlatformInfo{
{
Platform: SMTP.String(),
PlatformUrl: "",
PlatformFieldDescription: map[string]string{
"host": "host",
"port": "port",
"user": "user",
"pass": "pass",
"from": "from",
"ssl": "ssl",
},
},
}
}
+28
View File
@@ -0,0 +1,28 @@
package email
import (
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/email/smtp"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
type Sender interface {
Send(to []string, subject, body string) error
}
func NewSender(platform, config, siteName string) (Sender, error) {
switch parsePlatform(platform) {
case SMTP:
cfg := smtp.Config{}
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
logger.Error("unmarshal email config failed", logger.Field("error", err.Error()), logger.Field("config", config))
return nil, err
}
cfg.SiteName = siteName
return smtp.NewClient(&cfg), nil
default:
return nil, fmt.Errorf("unsupported platform: %s", platform)
}
}
+44
View File
@@ -0,0 +1,44 @@
package smtp
import (
"crypto/tls"
"gopkg.in/gomail.v2"
)
type Client struct {
conf Config
dailer *gomail.Dialer
}
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Pass string `json:"pass"`
From string `json:"from"`
SSL bool `json:"ssl"`
SiteName string `json:"siteName"`
}
func NewClient(conf *Config) *Client {
if conf == nil {
return nil
}
dailer := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
dailer.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
ServerName: conf.Host,
}
return &Client{conf: *conf, dailer: dailer}
}
func (m *Client) Send(to []string, subject, body string) error {
msg := gomail.NewMessage()
msg.SetAddressHeader("From", m.conf.From, m.conf.SiteName)
msg.SetHeader("To", to...)
msg.SetHeader("Subject", subject)
msg.SetBody("text/html", body)
return m.dailer.DialAndSend(msg)
}
+24
View File
@@ -0,0 +1,24 @@
package smtp
import "testing"
func TestEmailSend(t *testing.T) {
t.Skipf("Skip TestEmailSend")
config := &Config{
Host: "smtp.mail.me.com",
Port: 587,
User: "support@ppanel.dev",
Pass: "password",
From: "support@ppanel.dev",
SSL: true,
SiteName: "",
}
address := []string{"tension@sparkdance.dev"}
subject := "test"
body := "test"
email := NewClient(config)
err := email.Send(address, subject, body)
if err != nil {
t.Errorf("send email error: %v", err)
}
}
+364
View File
@@ -0,0 +1,364 @@
package email
const (
DefaultEmailVerifyTemplate = `<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
{{if eq .Type 1}}注册验证码 / Registration Verification Code{{else}}重置密码验证码 / Password
Reset Verification Code{{end}}
</title>
<style>
body {
color: black;
}
.container {
border-radius: 5px;
width: 500px;
margin: 20px auto 0;
border: 1px solid #cce7ff;
background-color: #f0f8ff;
padding: 25px 30px;
}
.header {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.logo {
width: 56px;
height: 56px;
object-fit: cover;
margin-right: 10px;
}
.site-name {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.content {
margin: 10px 0;
font-size: 14px;
}
.greeting {
font-weight: 700;
margin: 5px 0;
}
.highlight {
margin: 0 2px;
font-weight: 700;
color: #007bff;
}
.code-container {
margin: 25px 0;
width: 100%;
background-color: #e6f2ff;
height: 60px;
line-height: 60px;
text-align: center;
font-size: 32px;
font-weight: 700;
color: #007bff;
}
.code {
letter-spacing: 5pt;
}
.footer {
border-top: #99ccff 1px solid;
margin-top: 20px;
padding-top: 5px;
font-size: 12px;
font-weight: 700;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="{{.SiteLogo}}" class="logo" />
<p class="site-name">{{.SiteName}}</p>
</div>
<div class="content">
<p class="greeting">Hi, 尊敬的用户 / Dear User</p>
<p>
{{if eq .Type 1}} 感谢您注册!您的验证码是(请于<span class="highlight">{{.Expire}}</span
>分钟内使用):
<br />
Thank you for registering! Your verification code is (please use it within
<span class="highlight">{{.Expire}}</span> minutes): {{else}}
您正在重置密码。您的验证码是(请于<span class="highlight">{{.Expire}}</span>分钟内使用):
<br />
You are resetting your password. Your verification code is (please use it within
<span class="highlight">{{.Expire}}</span> minutes): {{end}}
</p>
<div class="code-container">
<span class="code">{{.Code}}</span>
</div>
<p>
如果您未请求此验证码,请忽略此邮件。<br />If you did not request this code, please ignore
this email.
</p>
</div>
<div class="footer">此为系统邮件,请勿回复 / This is a system email, please do not reply</div>
</div>
</body>
</html>
`
DefaultMaintenanceEmailTemplate = `
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>系统维护通知 / System Maintenance Notice</title>
<style>
body {
color: black;
}
.container {
border-radius: 5px;
width: 500px;
margin: 20px auto 0;
border: 1px solid #cce7ff;
background-color: #f0f8ff;
padding: 25px 30px;
}
.header {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.logo {
width: 56px;
height: 56px;
object-fit: cover;
margin-right: 10px;
}
.site-name {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.content {
margin: 20px 0;
font-size: 14px;
}
.greeting {
font-weight: 700;
margin: 5px 0;
}
.highlight {
margin: 0 2px;
font-weight: 700;
color: #007bff;
}
.footer {
border-top: #99ccff 1px solid;
margin-top: 20px;
padding-top: 5px;
font-size: 12px;
font-weight: 700;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="{{.SiteLogo}}" class="logo" />
<p class="site-name">{{.SiteName}}</p>
</div>
<div class="content">
<p class="greeting">Hi, 尊敬的用户 / Dear User</p>
<p>
我们计划在<span class="highlight">{{.MaintenanceDate}}</span
>进行系统维护,预计维护时间为<span class="highlight">{{.MaintenanceTime}}</span
>。在此期间,您可能会遇到服务中断或无法访问的情况。
<br />
We will be performing system maintenance on
<span class="highlight">{{.MaintenanceDate}}</span>, and the expected maintenance period
is <span class="highlight">{{.MaintenanceTime}}</span>. During this time, you may
experience service interruptions or unavailability.
</p>
<p>
维护完成后,系统将自动恢复。如果您有任何问题,请随时联系我们的支持团队。
<br />
The system will resume automatically once the maintenance is completed. If you have any
questions, please feel free to contact our support team.
</p>
</div>
<div class="footer">此为系统邮件,请勿回复 / This is a system email, please do not reply</div>
</div>
</body>
</html>
`
DefaultExpirationEmailTemplate = `<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>服务到期通知 / Service Expiration Notice</title>
<style>
body {
color: black;
}
.container {
border-radius: 5px;
width: 500px;
margin: 20px auto 0;
border: 1px solid #cce7ff;
background-color: #f0f8ff;
padding: 25px 30px;
}
.header {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.logo {
width: 56px;
height: 56px;
object-fit: cover;
margin-right: 10px;
}
.site-name {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.content {
margin: 20px 0;
font-size: 14px;
}
.greeting {
font-weight: 700;
margin: 5px 0;
}
.highlight {
margin: 0 2px;
font-weight: 700;
color: #007bff;
}
.footer {
border-top: #99ccff 1px solid;
margin-top: 20px;
padding-top: 5px;
font-size: 12px;
font-weight: 700;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="{{.SiteLogo}}" class="logo" />
<p class="site-name">{{.SiteName}}</p>
</div>
<div class="content">
<p class="greeting">Hi, 尊敬的用户 / Dear User</p>
<p>
您的服务即将在<span class="highlight">{{.ExpireDate}}</span
>到期,请及时续费以保证服务不间断。
<br />
Your service is set to expire on <span class="highlight">{{.ExpireDate}}</span>. Please
renew your subscription to avoid service interruptions.
</p>
<p>
如需帮助,请联系客服团队。感谢您的支持!
<br />
If you need assistance, please contact our support team. Thank you for your continued
support!
</p>
</div>
<div class="footer">此为系统邮件,请勿回复 / This is a system email, please do not reply</div>
</div>
</body>
</html>
`
DefaultTrafficExceedEmailTemplate = `<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>流量用尽通知 / Traffic Exhausted Notice</title>
<style>
.container {
border-radius: 5px;
width: 500px;
margin: 20px auto 0;
border: 1px solid #cce7ff;
background-color: #f0f8ff;
padding: 25px 30px;
}
.header {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.logo {
width: 56px;
height: 56px;
object-fit: cover;
margin-right: 10px;
}
.site-name {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.content {
margin: 20px 0;
font-size: 14px;
}
.greeting {
font-weight: 700;
margin: 5px 0;
}
.highlight {
color: #007bff;
}
.footer {
border-top: #99ccff 1px solid;
margin-top: 20px;
padding-top: 5px;
font-size: 12px;
font-weight: 700;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="{{.SiteLogo}}" class="logo" />
<p class="site-name">{{.SiteName}}</p>
</div>
<div class="content">
<p class="greeting">Hi, 尊敬的用户 / Dear User</p>
<p>
您的流量已经用尽,请及时购买流量以继续使用我们的服务。
<br />
Your traffic has been exhausted. Please purchase additional traffic to continue using our
service.
</p>
<p>
如需帮助,请联系客服团队。感谢您的支持!
<br />
If you need assistance, please contact our support team. Thank you for your continued
support!
</p>
</div>
<div class="footer">此为系统邮件,请勿回复 / This is a system email, please do not reply</div>
</div>
</body>
</html>`
)
+36
View File
@@ -0,0 +1,36 @@
package email
import (
"bytes"
"html/template"
"testing"
)
type VerifyTemplate struct {
Type uint8
SiteLogo string
SiteName string
Expire uint8
Code string
}
func TestVerifyEmail(t *testing.T) {
t.Skipf("Skip TestVerifyEmail test")
data := VerifyTemplate{
Type: 1,
SiteLogo: "https://www.google.com",
SiteName: "Google",
Expire: 5,
Code: "123456",
}
tpl, err := template.New("email").Parse(DefaultEmailVerifyTemplate)
if err != nil {
t.Error(err)
}
var result bytes.Buffer
err = tpl.Execute(&result, data)
if err != nil {
t.Error(err)
}
t.Log(result.String())
}