Issues (4)

app/rest/server.go (2 issues)

Severity
1
package rest
2
3
import (
4
	"bufio"
5
	"log"
6
	"net/http"
7
	"os"
8
	"path/filepath"
9
	"strings"
10
11
	"github.com/go-chi/chi"
12
	"github.com/go-chi/chi/middleware"
13
	"github.com/go-chi/render"
14
15
	"encoding/json"
16
	"fmt"
17
	"github.com/popstas/go-toggl"
18
	"github.com/popstas/planfix-go/planfix"
19
	"github.com/viasite/planfix-toggl-server/app/client"
20
	"github.com/viasite/planfix-toggl-server/app/config"
21
	"time"
22
)
23
24
// Server is a rest with store
25
type Server struct {
26
	Version     string
27
	TogglClient *client.TogglClient
28
	Config      *config.Config
29
	Logger      *log.Logger
30
}
31
32
//Run the lister and request's router, activate rest server
33
func (s Server) Run(portSSL int) {
34
	//port := 8096
35
	//s.Logger.Printf("[INFO] запуск сервера на :%d", port)
36
37
	router := chi.NewRouter()
38
	router.Use(middleware.RealIP, Recoverer)
39
	router.Use(AppInfo("planfix-toggl", s.Version), Ping)
40
	router.Use(CORS)
41
42
	router.Route("/api/v1", func(r chi.Router) {
43
		if s.Config.Debug {
44
			r.Use(Logger())
45
		}
46
47
		// toggl
48
		r.Route("/toggl", func(r chi.Router) {
49
			r.Get("/entries", s.getEntriesCtrl)
50
			r.Get("/entries/current", s.getTogglCurrentCtrl)
51
			r.Get("/entries/planfix/{taskID}", s.getPlanfixTaskCtrl)
52
			r.Get("/entries/planfix/{taskID}/last", s.getPlanfixTaskLastCtrl)
53
			r.Get("/user", s.getTogglUser)
54
			r.Get("/workspaces", s.getTogglWorkspaces)
55
		})
56
57
		// config
58
		r.Route("/config", func(r chi.Router) {
59
			r.Get("/", s.getConfigCtrl)
60
			r.Options("/", s.updateConfigCtrl)
61
			r.Post("/", s.updateConfigCtrl)
62
			r.Post("/reload", s.reloadConfigCtrl)
63
		})
64
65
		// planfix
66
		r.Route("/planfix", func(r chi.Router) {
67
			r.Get("/user", s.getPlanfixUser)
68
			r.Get("/analitics", s.getPlanfixAnalitics)
69
		})
70
71
		// validate
72
		r.Route("/validate", func(r chi.Router) {
73
			r.Get("/config", s.validateConfig)
74
			r.Get("/planfix/user", s.validatePlanfixUser)
75
			r.Get("/planfix/analitic", s.validatePlanfixAnalitic)
76
			r.Get("/toggl/user", s.validateTogglUser)
77
			r.Get("/toggl/workspace", s.validateTogglWorkspace)
78
		})
79
	})
80
81
	router.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
82
		render.PlainText(w, r, "User-agent: *\nDisallow: /api/")
83
	})
84
85
	router.Get("/log", func(w http.ResponseWriter, r *http.Request) {
86
		log := ""
87
88
		file, err := os.Open(s.TogglClient.Config.LogFile)
89
		if err != nil {
90
			render.Status(r, 400)
91
			render.PlainText(w, r, err.Error())
92
		}
93
		defer file.Close()
94
95
		fileScanner := bufio.NewScanner(file)
96
97
		for fileScanner.Scan() {
98
			line := fileScanner.Text()
99
			line = strings.Replace(line, "[planfix-toggl]", "", -1)
100
			log += fmt.Sprintf("%s<br>", line)
101
		}
102
103
		render.HTML(w, r, log)
104
	})
105
106
	s.fileServer(router, "/", http.Dir(filepath.Join(".", "docroot")))
107
108
	//go http.ListenAndServe(fmt.Sprintf(":%d", port), router)
109
	s.Logger.Printf("[INFO] веб-интерфейс на https://localhost:%d", portSSL)
110
	s.Logger.Println(http.ListenAndServeTLS(
111
		fmt.Sprintf(":%d", portSSL),
112
		"certs/server.crt",
113
		"certs/server.key", router),
114
	)
115
116
	//s.Logger.Printf("[INFO] веб-интерфейс на http://localhost:%d", port)
117
	//s.Logger.Println(http.ListenAndServe(fmt.Sprintf(":%d", port), router))
118
}
119
120
// GET /v1/toggl/entries
121
func (s Server) getEntriesCtrl(w http.ResponseWriter, r *http.Request) {
122
	var entries []client.TogglPlanfixEntry
123
	var err error
124
	queryValues := r.URL.Query()
125
	t := queryValues.Get("type")
126
	tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
127
128
	if t == "today" {
129
		entries, err = s.TogglClient.GetEntries(
130
			s.Config.TogglWorkspaceID,
131
			time.Now().Format("2006-01-02"),
132
			tomorrow,
133
		)
134
	} else if t == "pending" {
135
		entries, err = s.TogglClient.GetPendingEntries()
136
	} else if t == "last" {
137
		var pageEntries []client.TogglPlanfixEntry
138
		for currentPage := 1; currentPage <= 10; currentPage++ {
139
			pageEntries, err = s.TogglClient.GetEntriesV2(toggl.DetailedReportParams{
140
				Page: currentPage,
141
				Since: time.Now().AddDate(0, 0, -7),
142
				Until: time.Now().AddDate(0, 0, 1),
143
			})
144
			if err != nil {
145
				s.Logger.Printf("[WARN] failed to load entries")
146
				break
147
			}
148
			if len(pageEntries) == 0 {
149
				break
150
			}
151
152
			entries = append(entries, pageEntries...)
153
		}
154
	}
155
156
	entries = s.TogglClient.SumEntriesGroup(s.TogglClient.GroupEntriesByTask(entries))
157
158
	//render.Status(r, status)
159
	render.JSON(w, r, entries)
160
}
161
162
// GET /v1/config
163
func (s Server) getConfigCtrl(w http.ResponseWriter, r *http.Request) {
164
	render.JSON(w, r, config.GetConfig())
165
}
166
167
// POST /v1/config
168
func (s Server) updateConfigCtrl(w http.ResponseWriter, r *http.Request) {
169
	// answer to OPTIONS request for content-type
170
	if r.Method == "OPTIONS" {
171
		if r.Header.Get("Access-Control-Request-Method") == "content-type" {
172
			w.Header().Set("Content-Type", "application/json")
173
		}
174
		return
175
	}
176
177
	newConfig := config.GetConfig()
178
	decoder := json.NewDecoder(r.Body)
179
	err := decoder.Decode(&newConfig)
180
	if err != nil {
181
		s.Logger.Printf("[ERROR] Cannot decode %v", err.Error())
182
	}
183
184
	errors, _ := newConfig.Validate()
185
	//if len(errors) == 0 {
186
	newConfig.SaveConfig()
187
	//}
188
	render.JSON(w, r, errors)
189
}
190
191
// POST /v1/config/reload
192
func (s *Server) reloadConfigCtrl(w http.ResponseWriter, r *http.Request) {
193
	newConfig := config.GetConfig()
194
	s.Config = &newConfig
195
	s.TogglClient.Config = &newConfig
196
	s.TogglClient.ReloadConfig()
197
}
198
199
type ValidatorStatus struct {
200
	Ok     bool        `json:"ok"`
201
	Errors []string    `json:"errors"`
202
	Data   interface{} `json:"data"`
203
}
204
205
// GET /api/v1/validate/config
206
func (s Server) validateConfig(w http.ResponseWriter, r *http.Request) {
207
	v := client.ConfigValidator{s.Config}
208
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
209
}
210
211
// GET /api/v1/validate/planfix/user
212
func (s Server) validatePlanfixUser(w http.ResponseWriter, r *http.Request) {
213
	v := client.PlanfixUserValidator{s.TogglClient}
214
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
215
}
216
217
// GET /api/v1/validate/planfix/analitic
218
func (s Server) validatePlanfixAnalitic(w http.ResponseWriter, r *http.Request) {
219
	v := client.PlanfixAnaliticValidator{s.TogglClient}
220
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
221
}
222
223
// GET /api/v1/validate/toggl/user
224
func (s Server) validateTogglUser(w http.ResponseWriter, r *http.Request) {
225
	v := client.TogglUserValidator{s.TogglClient}
226
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
227
}
228
229
// GET /api/v1/validate/toggl/workspace
230
func (s Server) validateTogglWorkspace(w http.ResponseWriter, r *http.Request) {
231
	v := client.TogglWorkspaceValidator{s.TogglClient}
232
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
233
}
234
235
// GET /api/v1/planfix/user
236
func (s Server) getPlanfixUser(w http.ResponseWriter, r *http.Request) {
237
	v := client.PlanfixUserValidator{s.TogglClient}
238
	errors, ok, data := v.Check()
239
	render.JSON(w, r, ValidatorStatus{ok, errors, data})
240
}
241
242
// GET /api/v1/planfix/analitics
243
func (s Server) getPlanfixAnalitics(w http.ResponseWriter, r *http.Request) {
244
	var analiticList planfix.XMLResponseAnaliticGetList
245
	analiticList, err := s.TogglClient.PlanfixAPI.AnaliticGetList(0)
246
	if err != nil {
247
		render.Status(r, 400)
248
		render.PlainText(w, r, err.Error())
249
	}
250
251
	render.JSON(w, r, analiticList.Analitics)
252
}
253
254
// GET /api/v1/toggl/user
255
func (s Server) getTogglUser(w http.ResponseWriter, r *http.Request) {
256
	var user toggl.Account
257
	var errors []string
258
	user, err := s.TogglClient.Session.GetAccount()
259
	if err != nil {
260
		msg := "Не удалось получить Toggl UserID, проверьте TogglAPIToken, %s"
261
		errors = append(errors, fmt.Sprintf(msg, err.Error()))
0 ignored issues
show
can't check non-constant format in call to Sprintf
Loading history...
262
	}
263
264
	render.JSON(w, r, ValidatorStatus{err == nil, errors, user.Data})
265
}
266
267
// GET /api/v1/toggl/workspaces
268
func (s Server) getTogglWorkspaces(w http.ResponseWriter, r *http.Request) {
269
	var workspaces []toggl.Workspace
270
	var errors []string
271
	workspaces, err := s.TogglClient.Session.GetWorkspaces()
272
	if err != nil {
273
		msg := "Не удалось получить Toggl workspaces, проверьте TogglAPIToken, %s"
274
		errors = append(errors, fmt.Sprintf(msg, err.Error()))
0 ignored issues
show
can't check non-constant format in call to Sprintf
Loading history...
275
	}
276
277
	render.JSON(w, r, ValidatorStatus{err == nil, errors, workspaces})
278
}
279
280
// GET /toggl/entries/current
281
func (s Server) getTogglCurrentCtrl(w http.ResponseWriter, r *http.Request) {
282
	entry, _ := s.TogglClient.GetCurrentEntry()
283
	render.JSON(w, r, entry)
284
}
285
286
// GET /toggl/entries/planfix/{taskID}
287
func (s Server) getPlanfixTaskCtrl(w http.ResponseWriter, r *http.Request) {
288
	taskID := chi.URLParam(r, "taskID")
289
	entries, _ := s.TogglClient.GetEntriesByTag(taskID)
290
	render.JSON(w, r, entries)
291
}
292
293
// GET /toggl/entries/planfix/{taskID}/last
294
func (s Server) getPlanfixTaskLastCtrl(w http.ResponseWriter, r *http.Request) {
295
	taskID := chi.URLParam(r, "taskID")
296
	entries, _ := s.TogglClient.GetEntriesByTag(taskID)
297
	if len(entries) > 0 {
298
		render.JSON(w, r, entries[0])
299
	} else {
300
		render.JSON(w, r, entries)
301
	}
302
}
303
304
// serves static files from ./docroot
305
func (s Server) fileServer(r chi.Router, path string, root http.FileSystem) {
306
	//s.Logger.Printf("[INFO] run file server for %s", root)
307
	fs := http.StripPrefix(path, http.FileServer(root))
308
	if path != "/" && path[len(path)-1] != '/' {
309
		r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
310
		path += "/"
311
	}
312
	path += "*"
313
314
	r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
315
		// don't show dirs, just serve files
316
		if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != "/show/" {
317
			http.NotFound(w, r)
318
			return
319
		}
320
		fs.ServeHTTP(w, r)
321
	}))
322
}
323