Completed
Push — master ( 8eb6db...baa08e )
by Stanislav
01:34
created

rest.Server.getPlanfixAnalitics   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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