-
Notifications
You must be signed in to change notification settings - Fork 12
/
post.go
217 lines (192 loc) · 5.2 KB
/
post.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package models
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"github.com/movsb/taoblog/protocols"
)
const Untitled = `无标题`
// 文章的种类。
// 其实就是 type,准备换个名字。
type Kind string
// Post ...
type Post struct {
ID int64
Date int32
Modified int32
LastCommentedAt int32
Title string
Slug string
Type string
Category uint
Status string
PageView uint
CommentStatus uint
Comments uint
Metas PostMeta
Source string
SourceType string
}
// NOTE 如果要添加字段,记得同步 isEmpty 方法。
type PostMeta struct {
Header string `json:"header,omitempty" yaml:"header,omitempty"`
Footer string `json:"footer,omitempty" yaml:"footer,omitempty"`
Outdated bool `json:"outdated,omitempty" yaml:"outdated,omitempty"`
Wide bool `json:"wide,omitempty" yaml:"wide,omitempty"`
Weixin string `json:"weixin,omitempty" yaml:"weixin,omitempty"`
Sources map[string]*PostMetaSource `json:"sources,omitempty" yaml:"sources,omitempty"`
Geo *Geo `json:"geo,omitempty" yaml:"geo,omitempty"`
}
type PostMetaSource struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Time int32 `json:"time,omitempty" yaml:"time,omitempty"`
}
// 本来想用 GeoJSON 的,但是感觉标准化程度还不高。
// 想想还是算了,我只是想不丢失早期说说的地理位置信息。
// 那就用最简单的方式:经、纬度、名字,后期再升级吧。
// https://geojson.org/
// https://en.wikipedia.org/wiki/GeoJSON
type Geo struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` // 地理位置的名字
Longitude float32 `json:"longitude,omitempty" yaml:"longitude,omitempty"` // 经度
Latitude float32 `json:"latitude,omitempty" yaml:"latitude,omitempty"` // 纬度
}
var (
_ sql.Scanner = (*PostMeta)(nil)
_ driver.Valuer = (*PostMeta)(nil)
)
func (m PostMeta) Value() (driver.Value, error) {
return json.Marshal(m)
}
func (m *PostMeta) Scan(value interface{}) error {
switch v := value.(type) {
case string:
return json.Unmarshal([]byte(v), m)
case []byte:
return json.Unmarshal(v, m)
}
return errors.New(`unsupported type`)
}
func (m *PostMeta) ToProtocols() *protocols.Metas {
p := &protocols.Metas{
Header: m.Header,
Footer: m.Footer,
Outdated: m.Outdated,
Wide: m.Wide,
Weixin: m.Weixin,
Sources: make(map[string]*protocols.Metas_Source),
}
for name, src := range m.Sources {
p.Sources[name] = &protocols.Metas_Source{
Name: src.Name,
Url: src.URL,
Description: src.Description,
Time: src.Time,
}
}
if g := m.Geo; g != nil && g.Longitude != 0 && g.Latitude != 0 {
p.Geo = &protocols.Metas_Geo{
Longitude: g.Longitude,
Latitude: g.Latitude,
Name: g.Name,
}
}
return p
}
func (m *PostMeta) IsEmpty() bool {
return m.Header == "" &&
m.Footer == "" &&
!m.Outdated &&
!m.Wide &&
m.Weixin == "" &&
len(m.Sources) == 0 &&
(m.Geo == nil || (m.Geo.Longitude == 0 && m.Geo.Latitude == 0))
}
func PostMetaFrom(p *protocols.Metas) *PostMeta {
if p == nil {
p = &protocols.Metas{}
}
m := PostMeta{
Header: p.Header,
Footer: p.Footer,
Outdated: p.Outdated,
Wide: p.Wide,
Weixin: p.Weixin,
Sources: make(map[string]*PostMetaSource),
}
for name, src := range p.Sources {
m.Sources[name] = &PostMetaSource{
Name: src.Name,
URL: src.Url,
Description: src.Description,
Time: src.Time,
}
}
if g := p.Geo; g != nil {
m.Geo = &Geo{
Longitude: g.Longitude,
Latitude: g.Latitude,
Name: g.Name,
}
}
return &m
}
// TableName ...
func (Post) TableName() string {
return `posts`
}
// 以下字段由 setPostExtraFields 提供/清除。
// - Metas.Geo
// - Content
func (p *Post) ToProtocols(redact func(p *protocols.Post) error) (*protocols.Post, error) {
out := protocols.Post{
Id: p.ID,
Date: p.Date,
Modified: p.Modified,
Title: p.Title,
Slug: p.Slug,
Type: p.Type,
Category: int64(p.Category),
Status: p.Status,
PageView: int64(p.PageView),
CommentStatus: p.CommentStatus > 0,
Comments: int64(p.Comments),
Metas: p.Metas.ToProtocols(),
Source: p.Source,
SourceType: p.SourceType,
LastCommentedAt: p.LastCommentedAt,
}
err := redact(&out)
return &out, err
}
// Posts ...
type Posts []*Post
// ToProtocols ...
func (ps Posts) ToProtocols(redact func(p *protocols.Post) error) (posts []*protocols.Post, err error) {
for _, post := range ps {
if p, err := post.ToProtocols(redact); err != nil {
return nil, err
} else {
posts = append(posts, p)
}
}
return
}
type PostForRelated struct {
ID int64 `json:"id"`
Title string `json:"title"`
Relevance uint `json:"relevance"`
}
type Redirect struct {
ID int64
CreatedAt int32
SourcePath string
TargetPath string
StatusCode int
}
func (Redirect) TableName() string {
return `redirects`
}