-
Notifications
You must be signed in to change notification settings - Fork 0
/
route.go
231 lines (177 loc) · 6.84 KB
/
route.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package get_server
import (
"crypto/sha256"
"errors"
"fmt"
"net/http"
"strings"
"popplio/assetmanager"
"popplio/db"
"popplio/state"
"popplio/types"
docs "github.com/infinitybotlist/eureka/doclib"
"github.com/infinitybotlist/eureka/uapi"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
"github.com/go-chi/chi/v5"
)
var (
serverColsArr = db.GetCols(types.Server{})
serverCols = strings.Join(serverColsArr, ",")
teamColsArr = db.GetCols(types.Team{})
teamCols = strings.Join(teamColsArr, ",")
)
func Docs() *docs.Doc {
return &docs.Doc{
Summary: "Get Server",
Description: "Gets a server by id",
Params: []docs.Parameter{
{
Name: "id",
Description: "The servers ID",
Required: true,
In: "path",
Schema: docs.IdSchema,
},
{
Name: "target",
Description: `The target page of the request if any.
If target is 'page', then unique clicks will be counted based on a SHA-256 hashed IP
If target is 'invite', then the invite will be counted as a click
Officially recognized targets:
- page -> server page view
- settings -> server settings page view
- invite -> server invite view
- vote -> server vote page`,
Required: false,
In: "query",
Schema: docs.IdSchema,
},
{
Name: "include",
Description: "What extra fields to include, comma-seperated.\n`long` => server long description",
Required: false,
In: "query",
Schema: docs.IdSchema,
},
},
Resp: types.Bot{},
}
}
func handleAnalytics(r *http.Request, id, target string) error {
switch target {
case "page":
// Get IP from request and hash it
hashedIp := fmt.Sprintf("%x", sha256.Sum256([]byte(r.RemoteAddr)))
// Create transaction
tx, err := state.Pool.Begin(state.Context)
if err != nil {
return fmt.Errorf("error creating transaction: %w", err)
}
defer tx.Rollback(state.Context)
_, err = tx.Exec(state.Context, "UPDATE servers SET clicks = clicks + 1 WHERE server_id = $1", id)
if err != nil {
return fmt.Errorf("error updating clicks count: %w", err)
}
// Check if the IP has already clicked the server by checking the unique_clicks row
var hasClicked bool
err = tx.QueryRow(state.Context, "SELECT $1 = ANY(unique_clicks) FROM servers WHERE server_id = $2", hashedIp, id).Scan(&hasClicked)
if err != nil {
return fmt.Errorf("error checking for any unique clicks from this user: %w", err)
}
if !hasClicked {
// If not, add it to the array
state.Logger.Debug("Adding new unique click for user during handleAnalytics", zap.Error(err), zap.String("id", id), zap.String("target", target), zap.String("targetType", "bot"))
_, err = tx.Exec(state.Context, "UPDATE servers SET unique_clicks = array_append(unique_clicks, $1) WHERE server_id = $2", hashedIp, id)
if err != nil {
return fmt.Errorf("error adding new unique click for user: %w", err)
}
}
// Commit transaction
err = tx.Commit(state.Context)
if err != nil {
return fmt.Errorf("error committing transaction: %w", err)
}
case "invite":
// Update clicks
_, err := state.Pool.Exec(state.Context, "UPDATE servers SET invite_clicks = invite_clicks + 1 WHERE server_id = $1", id)
if err != nil {
return fmt.Errorf("error updating invite clicks: %w", err)
}
}
return nil
}
func Route(d uapi.RouteData, r *http.Request) uapi.HttpResponse {
id := chi.URLParam(r, "id")
target := r.URL.Query().Get("target")
row, err := state.Pool.Query(d.Context, "SELECT "+serverCols+" FROM servers WHERE server_id = $1", id)
if err != nil {
state.Logger.Error("Error while getting server [db fetch]", zap.Error(err), zap.String("id", id), zap.String("target", target))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
server, err := pgx.CollectOneRow(row, pgx.RowToStructByName[types.Server])
if errors.Is(err, pgx.ErrNoRows) {
return uapi.DefaultResponse(http.StatusNotFound)
}
if err != nil {
state.Logger.Error("Error while getting server [db collect]", zap.Error(err), zap.String("id", id), zap.String("target", target))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
row, err = state.Pool.Query(d.Context, "SELECT "+teamCols+" FROM teams WHERE id = $1", server.TeamOwnerID)
if err != nil {
state.Logger.Error("Error while getting team [db fetch]", zap.Error(err), zap.String("id", id), zap.String("target", target))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
eto, err := pgx.CollectOneRow(row, pgx.RowToStructByName[types.Team])
if err != nil {
state.Logger.Error("Error while getting team [db collect]", zap.Error(err), zap.String("id", id), zap.String("target", target))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
eto.Entities = &types.TeamEntities{
Targets: []string{}, // We don't provide any entities right now, may change
}
eto.Banner = assetmanager.BannerInfo(assetmanager.AssetTargetTypeTeams, eto.ID)
eto.Avatar = assetmanager.AvatarInfo(assetmanager.AssetTargetTypeTeams, eto.ID)
server.TeamOwner = &eto
var uniqueClicks int64
err = state.Pool.QueryRow(d.Context, "SELECT cardinality(unique_clicks) AS unique_clicks FROM servers WHERE server_id = $1", server.ServerID).Scan(&uniqueClicks)
if err != nil {
state.Logger.Error("Error while getting unique clicks", zap.Error(err), zap.String("id", id), zap.String("target", target))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
server.UniqueClicks = uniqueClicks
var code string
err = state.Pool.QueryRow(d.Context, "SELECT code FROM vanity WHERE itag = $1", server.VanityRef).Scan(&code)
if err != nil {
state.Logger.Error("Error while getting bot vanity code [db collect]", zap.Error(err), zap.String("id", id), zap.String("target", target), zap.String("serverID", server.ServerID))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
server.Vanity = code
server.Banner = assetmanager.BannerInfo(assetmanager.AssetTargetTypeServers, server.ServerID)
// Handle extra includes
if r.URL.Query().Get("include") != "" {
includesSplit := strings.Split(r.URL.Query().Get("include"), ",")
for _, include := range includesSplit {
switch include {
case "long":
// Fetch long description
var long string
err := state.Pool.QueryRow(d.Context, "SELECT long FROM servers WHERE server_id = $1", server.ServerID).Scan(&long)
if err != nil {
state.Logger.Error("Error while getting bot server description [db fetch]", zap.Error(err), zap.String("id", id), zap.String("target", target), zap.String("serverID", server.ServerID))
return uapi.DefaultResponse(http.StatusInternalServerError)
}
server.Long = long
}
}
}
go func() {
err = handleAnalytics(r, id, target)
if err != nil {
state.Logger.Error("Error while handling analytics", zap.Error(err), zap.String("id", id), zap.String("target", target))
}
}()
return uapi.HttpResponse{
Json: server,
}
}