Passed
Push — master ( 5c45e4...0c6d42 )
by Stanislav
01:45
created

rest.Server.getPlanfixTaskCtrl   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
eloc 4
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
		var pageEntries []client.TogglPlanfixEntry
113
		for currentPage := 1; currentPage <= 10; currentPage++ {
114
			pageEntries, err = s.TogglClient.GetEntriesV2(toggl.DetailedReportParams{
115
				Page: currentPage,
116
				Since: time.Now().AddDate(0, 0, -7),
117
				Until: time.Now().AddDate(0, 0, 1),
118
			})
119
			if err != nil {
120
				s.Logger.Printf("[WARN] failed to load entries")
121
				break
122
			}
123
			if len(pageEntries) == 0 {
124
				break
125
			}
126
127
			entries = append(entries, pageEntries...)
128
		}
129
	}
130
131
	entries = s.TogglClient.SumEntriesGroup(s.TogglClient.GroupEntriesByTask(entries))
132
133
	//render.Status(r, status)
134
	render.JSON(w, r, entries)
135
}
136
137
// GET /v1/config
138
func (s Server) getConfigCtrl(w http.ResponseWriter, r *http.Request) {
139
	render.JSON(w, r, config.GetConfig())
140
}
141
142
// POST /v1/config
143
func (s Server) updateConfigCtrl(w http.ResponseWriter, r *http.Request) {
144
	// answer to OPTIONS request for content-type
145
	if r.Method == "OPTIONS" {
146
		if r.Header.Get("Access-Control-Request-Method") == "content-type" {
147
			w.Header().Set("Content-Type", "application/json")
148
		}
149
		return
150
	}
151
152
	newConfig := config.GetConfig()
153
	decoder := json.NewDecoder(r.Body)
154
	err := decoder.Decode(&newConfig)
155
	if err != nil {
156
		s.Logger.Printf("[ERROR] Cannot decode %v", err.Error())
157
	}
158
159
	errors, _ := newConfig.Validate()
160
	if len(errors) == 0 {
161
		newConfig.SaveConfig()
162
	}
163
	render.JSON(w, r, errors)
164
}
165
166
// POST /v1/config/reload
167
func (s *Server) reloadConfigCtrl(w http.ResponseWriter, r *http.Request) {
168
	newConfig := config.GetConfig()
169
	s.Config = &newConfig
170
	s.TogglClient.Config = &newConfig
171
	s.TogglClient.ReloadConfig()
172
}
173
174
type ValidatorStatus struct {
0 ignored issues
show
introduced by
exported type ValidatorStatus should have comment or be unexported
Loading history...
175
	Ok     bool        `json:"ok"`
176
	Errors []string    `json:"errors"`
177
	Data   interface{} `json:"data"`
178
}
179
180
// GET /api/v1/validate/config
181
func (s Server) validateConfig(w http.ResponseWriter, r *http.Request) {
182
	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...
183
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
184
}
185
186
// GET /api/v1/validate/planfix/user
187
func (s Server) validatePlanfixUser(w http.ResponseWriter, r *http.Request) {
188
	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...
189
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
190
}
191
192
// GET /api/v1/validate/planfix/analitic
193
func (s Server) validatePlanfixAnalitic(w http.ResponseWriter, r *http.Request) {
194
	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...
195
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
196
}
197
198
// GET /api/v1/validate/toggl/user
199
func (s Server) validateTogglUser(w http.ResponseWriter, r *http.Request) {
200
	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...
201
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
202
}
203
204
// GET /api/v1/validate/toggl/workspace
205
func (s Server) validateTogglWorkspace(w http.ResponseWriter, r *http.Request) {
206
	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...
207
	render.JSON(w, r, client.StatusFromCheck(v.Check()))
208
}
209
210
// GET /api/v1/planfix/user
211
func (s Server) getPlanfixUser(w http.ResponseWriter, r *http.Request) {
212
	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...
213
	errors, ok, data := v.Check()
214
	render.JSON(w, r, ValidatorStatus{ok, errors, data})
215
}
216
217
// GET /api/v1/planfix/analitics
218
func (s Server) getPlanfixAnalitics(w http.ResponseWriter, r *http.Request) {
219
	var analiticList planfix.XMLResponseAnaliticGetList
220
	analiticList, err := s.TogglClient.PlanfixAPI.AnaliticGetList(0)
221
	if err != nil {
222
		render.Status(r, 400)
223
		render.PlainText(w, r, err.Error())
224
	}
225
226
	render.JSON(w, r, analiticList.Analitics)
227
}
228
229
// GET /api/v1/toggl/user
230
func (s Server) getTogglUser(w http.ResponseWriter, r *http.Request) {
231
	var user toggl.Account
232
	var errors []string
233
	user, err := s.TogglClient.Session.GetAccount()
234
	if err != nil {
235
		msg := "Не удалось получить Toggl UserID, проверьте TogglAPIToken, %s"
236
		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...
237
	}
238
239
	render.JSON(w, r, ValidatorStatus{err == nil, errors, user.Data})
240
}
241
242
// GET /api/v1/toggl/workspaces
243
func (s Server) getTogglWorkspaces(w http.ResponseWriter, r *http.Request) {
244
	var workspaces []toggl.Workspace
245
	var errors []string
246
	workspaces, err := s.TogglClient.Session.GetWorkspaces()
247
	if err != nil {
248
		msg := "Не удалось получить Toggl workspaces, проверьте TogglAPIToken, %s"
249
		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...
250
	}
251
252
	render.JSON(w, r, ValidatorStatus{err == nil, errors, workspaces})
253
}
254
255
// GET /toggl/entries/planfix/{taskID}
256
func (s Server) getPlanfixTaskCtrl(w http.ResponseWriter, r *http.Request) {
257
	taskID := chi.URLParam(r, "taskID")
258
	entries, _ := s.TogglClient.GetEntriesByTag(taskID)
259
	render.JSON(w, r, entries)
260
}
261
262
// GET /toggl/entries/planfix/{taskID}/last
263
func (s Server) getPlanfixTaskLastCtrl(w http.ResponseWriter, r *http.Request) {
264
	taskID := chi.URLParam(r, "taskID")
265
	entries, _ := s.TogglClient.GetEntriesByTag(taskID)
266
	if len(entries) > 0 {
267
		render.JSON(w, r, entries[0])
268
	} else {
269
		render.JSON(w, r, entries)
270
	}
271
}
272
273
// serves static files from ./docroot
274
func (s Server) fileServer(r chi.Router, path string, root http.FileSystem) {
275
	//s.Logger.Printf("[INFO] run file server for %s", root)
276
	fs := http.StripPrefix(path, http.FileServer(root))
277
	if path != "/" && path[len(path)-1] != '/' {
278
		r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
279
		path += "/"
280
	}
281
	path += "*"
282
283
	r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
284
		// don't show dirs, just serve files
285
		if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != "/show/" {
286
			http.NotFound(w, r)
287
			return
288
		}
289
		fs.ServeHTTP(w, r)
290
	}))
291
}
292