Passed
Push — master ( 7456e0...8eb6db )
by Stanislav
01:27
created

rest.Server.validateTogglWorkspace   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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