Compare commits
7 Commits
0b0917b877
...
a5322f8d92
Author | SHA1 | Date |
---|---|---|
Evan Chen | a5322f8d92 | |
Evan Chen | 4ccf2db1a2 | |
Evan Chen | 716f19133d | |
Evan Chen | 7e89c07951 | |
Evan Chen | 905e1779a5 | |
Evan Chen | c81b014919 | |
Evan Chen | b232685b63 |
2
Makefile
2
Makefile
|
@ -5,4 +5,4 @@ run:
|
||||||
APP_LOG_PRETTY=true \
|
APP_LOG_PRETTY=true \
|
||||||
APP_DB_TYPE=sqlite \
|
APP_DB_TYPE=sqlite \
|
||||||
APP_DATA=work \
|
APP_DATA=work \
|
||||||
go run main.go
|
go run cmd/test/main.go
|
|
@ -1,3 +1,9 @@
|
||||||
# Kumoly App
|
# Kumoly App
|
||||||
|
|
||||||
combine all needed module into one lib
|
combine all needed module into one lib
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kumoly.io/kumoly/app/auth"
|
"kumoly.io/kumoly/app/auth"
|
||||||
|
"kumoly.io/kumoly/app/history"
|
||||||
"kumoly.io/kumoly/app/server"
|
"kumoly.io/kumoly/app/server"
|
||||||
"kumoly.io/kumoly/app/store"
|
"kumoly.io/kumoly/app/store"
|
||||||
"kumoly.io/kumoly/app/system"
|
"kumoly.io/kumoly/app/system"
|
||||||
|
@ -17,7 +18,7 @@ func main() {
|
||||||
auth.SetDB(store.DB)
|
auth.SetDB(store.DB)
|
||||||
|
|
||||||
sys.Inject(auth.Injector(server.API))
|
sys.Inject(auth.Injector(server.API))
|
||||||
sys.Append(server, auth.New(server), &task.Service{})
|
sys.Append(server, auth.New(server), &task.Service{}, &history.Service{})
|
||||||
sys.Start()
|
sys.Start()
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package history
|
||||||
|
|
||||||
|
import "kumoly.io/kumoly/app/util"
|
||||||
|
|
||||||
|
func Error() *History {
|
||||||
|
h := getBase()
|
||||||
|
h.Type = ERROR
|
||||||
|
if !PROD {
|
||||||
|
h.Trace = util.Stack()
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info() *History {
|
||||||
|
h := getBase()
|
||||||
|
h.Type = INFO
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBase() *History {
|
||||||
|
mod, file := util.CallerMod(3)
|
||||||
|
h := &History{
|
||||||
|
Module: mod,
|
||||||
|
Caller: file,
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"kumoly.io/kumoly/app/errors"
|
||||||
|
"kumoly.io/kumoly/app/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PROD = false
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR = "ERROR"
|
||||||
|
INFO = "INFO"
|
||||||
|
)
|
||||||
|
|
||||||
|
type History struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
|
||||||
|
Module string
|
||||||
|
Type string
|
||||||
|
|
||||||
|
Message string
|
||||||
|
BodyJson string
|
||||||
|
Body interface{} `gorm:"-"`
|
||||||
|
Issuer string
|
||||||
|
Caller string
|
||||||
|
Trace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *History) BeforeCreate(tx *gorm.DB) (err error) {
|
||||||
|
if h.Body != nil {
|
||||||
|
body, err := json.Marshal(h.Body)
|
||||||
|
if err != nil {
|
||||||
|
h.BodyJson = string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var Tunnel chan *History
|
||||||
|
var quit chan struct{}
|
||||||
|
var started chan struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Tunnel = make(chan *History)
|
||||||
|
quit = make(chan struct{}, 1)
|
||||||
|
started = make(chan struct{}, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(r Receiver) {
|
||||||
|
select {
|
||||||
|
case started <- struct{}{}:
|
||||||
|
default:
|
||||||
|
panic(errors.New(500, "history reporter has already started!"))
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case h := <-Tunnel:
|
||||||
|
if Interceptor != nil {
|
||||||
|
go Interceptor(h)
|
||||||
|
}
|
||||||
|
r(h)
|
||||||
|
case <-quit:
|
||||||
|
<-started
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
quit <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Send(h *History) {
|
||||||
|
if h.Type == "" {
|
||||||
|
h.Type = INFO
|
||||||
|
}
|
||||||
|
Tunnel <- h
|
||||||
|
}
|
||||||
|
|
||||||
|
type Receiver func(*History)
|
||||||
|
|
||||||
|
var Interceptor func(*History) = nil
|
||||||
|
|
||||||
|
var DBReceiver Receiver = func(h *History) {
|
||||||
|
err := store.DB.Create(h).Error
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Str("mod", "history").Err(err).Msg("DBReceiver error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ConsoleReceiver Receiver = func(h *History) {
|
||||||
|
fmt.Printf("%+v\n", h)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"kumoly.io/kumoly/app/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHistory(t *testing.T) {
|
||||||
|
t.Log("start")
|
||||||
|
Start(func(h *History) {
|
||||||
|
t.Logf("%+v\n", h)
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
Send(&History{
|
||||||
|
Module: "test",
|
||||||
|
Type: INFO,
|
||||||
|
Message: "testing",
|
||||||
|
Body: time.Now().String(),
|
||||||
|
Caller: util.Caller(2),
|
||||||
|
})
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 20)
|
||||||
|
Stop()
|
||||||
|
t.Log("stoped")
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"kumoly.io/kumoly/app/store"
|
||||||
|
"kumoly.io/kumoly/app/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
system.BaseService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv Service) GetName() string { return "history.Service" }
|
||||||
|
func (srv Service) IsService() bool { return true }
|
||||||
|
func (srv Service) Init() error {
|
||||||
|
PROD = viper.GetBool("prod")
|
||||||
|
l := log.With().Str("mod", "history").
|
||||||
|
Str("service", "history.Service").
|
||||||
|
Logger()
|
||||||
|
l.Debug().Msg("Migrating database for history.Service ...")
|
||||||
|
if err := store.Migrate(&History{}); err != nil {
|
||||||
|
l.Error().Err(err).Msg("Migrating database")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (srv Service) Main() error {
|
||||||
|
Start(DBReceiver)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (srv Service) Del() {
|
||||||
|
Stop()
|
||||||
|
}
|
|
@ -0,0 +1,476 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<style type="text/css" rel="stylesheet" media="all">
|
||||||
|
/* Base ------------------------------ */
|
||||||
|
*:not(br):not(tr):not(html) {
|
||||||
|
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
background-color: #F2F4F6;
|
||||||
|
color: #74787E;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #3869D4;
|
||||||
|
}
|
||||||
|
/* Layout ------------------------------ */
|
||||||
|
.email-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #F2F4F6;
|
||||||
|
}
|
||||||
|
.email-content {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/* Masthead ----------------------- */
|
||||||
|
.email-masthead {
|
||||||
|
padding: 25px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.email-masthead_logo {
|
||||||
|
max-width: 400px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.email-masthead_name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2F3133;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 1px 0 white;
|
||||||
|
}
|
||||||
|
.email-logo {
|
||||||
|
max-height: 50px;
|
||||||
|
}
|
||||||
|
/* Body ------------------------------ */
|
||||||
|
.email-body {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-top: 1px solid #EDEFF2;
|
||||||
|
border-bottom: 1px solid #EDEFF2;
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.email-body_inner {
|
||||||
|
width: 570px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.email-footer {
|
||||||
|
width: 570px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.email-footer p {
|
||||||
|
color: #AEAEAE;
|
||||||
|
}
|
||||||
|
.body-action {
|
||||||
|
width: 100%;
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.body-dictionary {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 20px auto 10px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.body-dictionary dd {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
.body-dictionary dt {
|
||||||
|
clear: both;
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.body-dictionary dd {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.body-sub {
|
||||||
|
margin-top: 25px;
|
||||||
|
padding-top: 25px;
|
||||||
|
border-top: 1px solid #EDEFF2;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.body-sub a {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.content-cell {
|
||||||
|
padding: 35px;
|
||||||
|
}
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
/* Type ------------------------------ */
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
margin: 25px 0;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 10px solid #F0F2F4;
|
||||||
|
}
|
||||||
|
blockquote p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
blockquote cite {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
cite {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.925rem;
|
||||||
|
}
|
||||||
|
cite:before {
|
||||||
|
content: "\2014 \0020";
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #74787E;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
p.sub {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
p.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
padding: 0px 5px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #EDEFF2;
|
||||||
|
}
|
||||||
|
th p {
|
||||||
|
margin: 0;
|
||||||
|
color: #9BA2AB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 10px 5px;
|
||||||
|
color: #74787E;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
align: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/* Data table ------------------------------ */
|
||||||
|
.data-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 35px 0;
|
||||||
|
}
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.data-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px 5px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #EDEFF2;
|
||||||
|
}
|
||||||
|
.data-table th p {
|
||||||
|
margin: 0;
|
||||||
|
color: #9BA2AB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.data-table td {
|
||||||
|
padding: 10px 5px;
|
||||||
|
color: #74787E;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
/* Invite Code ------------------------------ */
|
||||||
|
.invite-code {
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-right: 36px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-left: 36px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: Consolas, monaco, monospace;
|
||||||
|
font-size: 28px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
color: #555;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
/* Buttons ------------------------------ */
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #3869D4;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 45px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
mso-hide: all;
|
||||||
|
}
|
||||||
|
/*Media Queries ------------------------------ */
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.email-body_inner,
|
||||||
|
.email-footer {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 500px) {
|
||||||
|
.button {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td class="content">
|
||||||
|
<table class="email-content" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<!-- Logo -->
|
||||||
|
<tr>
|
||||||
|
<td class="email-masthead">
|
||||||
|
<a class="email-masthead_name" href="{{.Hermes.Product.Link}}" target="_blank">
|
||||||
|
{{ if .Hermes.Product.Logo }}
|
||||||
|
<img src="{{.Hermes.Product.Logo | url }}" class="email-logo" />
|
||||||
|
{{ else }}
|
||||||
|
{{ .Hermes.Product.Name }}
|
||||||
|
{{ end }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Email Body -->
|
||||||
|
<tr>
|
||||||
|
<td class="email-body" width="100%">
|
||||||
|
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0">
|
||||||
|
<!-- Body content -->
|
||||||
|
<tr>
|
||||||
|
<td class="content-cell">
|
||||||
|
<h1>{{if .Email.Body.Title }}{{ .Email.Body.Title }}{{ else }}{{ .Email.Body.Greeting }} {{ .Email.Body.Name }},{{ end }}</h1>
|
||||||
|
{{ with .Email.Body.Intros }}
|
||||||
|
{{ if gt (len .) 0 }}
|
||||||
|
{{ range $line := . }}
|
||||||
|
<p>{{ $line }}</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if (ne .Email.Body.FreeMarkdown "") }}
|
||||||
|
{{ .Email.Body.FreeMarkdown.ToHTML }}
|
||||||
|
{{ else }}
|
||||||
|
{{ with .Email.Body.Dictionary }}
|
||||||
|
{{ if gt (len .) 0 }}
|
||||||
|
<dl class="body-dictionary">
|
||||||
|
{{ range $entry := . }}
|
||||||
|
<dt>{{ $entry.Key }}:</dt>
|
||||||
|
<dd>{{ $entry.Value }}</dd>
|
||||||
|
{{ end }}
|
||||||
|
</dl>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<!-- Table -->
|
||||||
|
{{ with .Email.Body.Table }}
|
||||||
|
{{ $data := .Data }}
|
||||||
|
{{ $columns := .Columns }}
|
||||||
|
{{ if gt (len $data) 0 }}
|
||||||
|
<table class="data-wrapper" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<table class="data-table" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
{{ $col := index $data 0 }}
|
||||||
|
{{ range $entry := $col }}
|
||||||
|
<th
|
||||||
|
{{ with $columns }}
|
||||||
|
{{ $width := index .CustomWidth $entry.Key }}
|
||||||
|
{{ with $width }}
|
||||||
|
width="{{ . }}"
|
||||||
|
{{ end }}
|
||||||
|
{{ $align := index .CustomAlignment $entry.Key }}
|
||||||
|
{{ with $align }}
|
||||||
|
style="text-align:{{ . }}"
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
>
|
||||||
|
<p>{{ $entry.Key }}</p>
|
||||||
|
</th>
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
{{ range $row := $data }}
|
||||||
|
<tr>
|
||||||
|
{{ range $cell := $row }}
|
||||||
|
<td
|
||||||
|
{{ with $columns }}
|
||||||
|
{{ $align := index .CustomAlignment $cell.Key }}
|
||||||
|
{{ with $align }}
|
||||||
|
style="text-align:{{ . }}"
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
>
|
||||||
|
{{ $cell.Value }}
|
||||||
|
</td>
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<!-- Action -->
|
||||||
|
{{ with .Email.Body.Actions }}
|
||||||
|
{{ if gt (len .) 0 }}
|
||||||
|
{{ range $action := . }}
|
||||||
|
<p>{{ $action.Instructions }}</p>
|
||||||
|
{{ $length := len $action.Button.Text }}
|
||||||
|
{{ $width := add (mul $length 9) 20 }}
|
||||||
|
{{if (lt $width 200)}}{{$width = 200}}{{else if (gt $width 570)}}{{$width = 570}}{{else}}{{end}}
|
||||||
|
{{safe "<!--[if mso]>" }}
|
||||||
|
{{ if $action.Button.Text }}
|
||||||
|
<div style="margin: 30px auto;v-text-anchor:middle;text-align:center">
|
||||||
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:w="urn:schemas-microsoft-com:office:word"
|
||||||
|
href="{{ $action.Button.Link }}"
|
||||||
|
style="height:45px;v-text-anchor:middle;width:{{$width}}px;background-color:{{ if $action.Button.Color }}{{ $action.Button.Color }}{{ else }}#3869D4{{ end }};"
|
||||||
|
arcsize="10%"
|
||||||
|
{{ if $action.Button.Color }}strokecolor="{{ $action.Button.Color }}" fillcolor="{{ $action.Button.Color }}"{{ else }}strokecolor="#3869D4" fillcolor="#3869D4"{{ end }}
|
||||||
|
>
|
||||||
|
<w:anchorlock/>
|
||||||
|
<center style="color: {{ if $action.Button.TextColor }}{{ $action.Button.TextColor }}{{else}}#FFFFFF{{ end }};font-size: 15px;text-align: center;font-family:sans-serif;font-weight:bold;">
|
||||||
|
{{ $action.Button.Text }}
|
||||||
|
</center>
|
||||||
|
</v:roundrect>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ if $action.InviteCode }}
|
||||||
|
<div style="margin-top:30px;margin-bottom:30px">
|
||||||
|
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table align="center" cellpadding="0" cellspacing="0" style="padding:0;text-align:center">
|
||||||
|
<tr>
|
||||||
|
<td style="display:inline-block;border-radius:3px;font-family:Consolas, monaco, monospace;font-size:28px;text-align:center;letter-spacing:8px;color:#555;background-color:#eee;padding:20px">
|
||||||
|
{{ $action.InviteCode }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{safe "<![endif]-->" }}
|
||||||
|
{{safe "<!--[if !mso]><!-- -->"}}
|
||||||
|
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<div>
|
||||||
|
{{ if $action.Button.Text }}
|
||||||
|
<a href="{{ $action.Button.Link }}" class="button" style="{{ with $action.Button.Color }}background-color: {{ . }};{{ end }} {{ with $action.Button.TextColor }}color: {{ . }};{{ end }} width: {{$width}}px;" target="_blank">
|
||||||
|
{{ $action.Button.Text }}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
{{ if $action.InviteCode }}
|
||||||
|
<span class="invite-code">{{ $action.InviteCode }}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{safe "<![endif]-->" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ with .Email.Body.Outros }}
|
||||||
|
{{ if gt (len .) 0 }}
|
||||||
|
{{ range $line := . }}
|
||||||
|
<p>{{ $line }}</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<p>
|
||||||
|
{{.Email.Body.Signature}},
|
||||||
|
<br />
|
||||||
|
{{.Hermes.Product.Name}}
|
||||||
|
</p>
|
||||||
|
{{ if (eq .Email.Body.FreeMarkdown "") }}
|
||||||
|
{{ with .Email.Body.Actions }}
|
||||||
|
<table class="body-sub">
|
||||||
|
<tbody>
|
||||||
|
{{ range $action := . }}
|
||||||
|
{{if $action.Button.Text}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p class="sub">{{$.Hermes.Product.TroubleText | replace "{ACTION}" $action.Button.Text}}</p>
|
||||||
|
<p class="sub"><a href="{{ $action.Button.Link }}">{{ $action.Button.Link }}</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td class="content-cell">
|
||||||
|
<p class="sub center">
|
||||||
|
{{.Hermes.Product.Copyright}}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
package messenger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
export APP_SERVER_HOST=127.0.0.1
|
||||||
|
export APP_SERVER_PORT=8000
|
||||||
|
export APP_LOG_LEVEL=-1
|
||||||
|
export APP_PROD=false
|
||||||
|
export APP_LOG_PRETTY=true
|
||||||
|
export APP_DB_TYPE=sqlite
|
||||||
|
export APP_DATA=work
|
||||||
|
go run cmd/test/main.go
|
|
@ -9,7 +9,7 @@ import (
|
||||||
type BaseService struct{}
|
type BaseService struct{}
|
||||||
|
|
||||||
func (srv BaseService) GetName() string { return "base" }
|
func (srv BaseService) GetName() string { return "base" }
|
||||||
func (srv BaseService) GetDependencies() []string { return []string{""} }
|
func (srv BaseService) GetDependencies() []string { return []string{} }
|
||||||
func (srv BaseService) IsService() bool { return false }
|
func (srv BaseService) IsService() bool { return false }
|
||||||
func (srv BaseService) Init() error { return nil }
|
func (srv BaseService) Init() error { return nil }
|
||||||
func (srv BaseService) Load() error { return nil }
|
func (srv BaseService) Load() error { return nil }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -42,6 +43,7 @@ func init() {
|
||||||
viper.SetEnvKeyReplacer(replacer)
|
viper.SetEnvKeyReplacer(replacer)
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
viper.SetConfigType("json")
|
viper.SetConfigType("json")
|
||||||
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
|
@ -60,9 +62,24 @@ func setup() {
|
||||||
if cc, ok := i.(string); ok {
|
if cc, ok := i.(string); ok {
|
||||||
c = cc
|
c = cc
|
||||||
}
|
}
|
||||||
// if len(c) > 0 {
|
if len(c) > 0 {
|
||||||
|
// shorten caller to mod/file:line
|
||||||
// }
|
segs := strings.Split(c, ":")
|
||||||
|
file := segs[len(segs)-2]
|
||||||
|
short := file
|
||||||
|
skip := false
|
||||||
|
for i := len(file) - 1; i > 0; i-- {
|
||||||
|
if file[i] == '/' {
|
||||||
|
if !skip {
|
||||||
|
skip = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
short = file[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v:%v", short, segs[len(segs)-1])
|
||||||
|
}
|
||||||
return c
|
return c
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -98,7 +98,7 @@ func New() *System {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) Start() {
|
func (sys *System) Start() {
|
||||||
setup()
|
// setup()
|
||||||
sys.order()
|
sys.order()
|
||||||
sys.status = sys_init
|
sys.status = sys_init
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -7,11 +7,39 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Stack() string {
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
n := runtime.Stack(buf, false)
|
||||||
|
if n < len(buf) {
|
||||||
|
return string(buf[:n])
|
||||||
|
}
|
||||||
|
buf = make([]byte, 2*len(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CallerFull(depth int) string {
|
func CallerFull(depth int) string {
|
||||||
_, file, line, _ := runtime.Caller(depth)
|
_, file, line, _ := runtime.Caller(depth)
|
||||||
return fmt.Sprintf("%s:%d", file, line)
|
return fmt.Sprintf("%s:%d", file, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CallerMod(depth int) (mod, file string) {
|
||||||
|
_, f, l, _ := runtime.Caller(depth)
|
||||||
|
ptr := 0
|
||||||
|
for i := len(f) - 1; i > 0; i-- {
|
||||||
|
if f[i] == '/' {
|
||||||
|
if ptr == 0 {
|
||||||
|
file = fmt.Sprintf("%v:%v", f[i+1:], l)
|
||||||
|
ptr = i
|
||||||
|
} else {
|
||||||
|
mod = f[i+1 : ptr]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func Caller(depth int) string {
|
func Caller(depth int) string {
|
||||||
_, file, line, _ := runtime.Caller(depth)
|
_, file, line, _ := runtime.Caller(depth)
|
||||||
short := file
|
short := file
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStack(t *testing.T) {
|
||||||
|
fmt.Println(Stack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerFull(t *testing.T) {
|
||||||
|
fmt.Println(CallerFull(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallerMod(t *testing.T) {
|
||||||
|
fmt.Println(CallerMod(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCaller(t *testing.T) {
|
||||||
|
fmt.Println(Caller(1))
|
||||||
|
}
|
Loading…
Reference in New Issue