api.go 9.84 KB
Newer Older
Lukas Böhm's avatar
Lukas Böhm committed
1
2
3
package main

import (
Lukas Böhm's avatar
Lukas Böhm committed
4
	"archive/zip"
Lukas Böhm's avatar
Lukas Böhm committed
5
	"encoding/json"
Lukas Böhm's avatar
Lukas Böhm committed
6
	"errors"
Lukas Böhm's avatar
Lukas Böhm committed
7
	"fmt"
Lukas Böhm's avatar
Lukas Böhm committed
8
	"github.com/google/uuid"
Lukas Böhm's avatar
Lukas Böhm committed
9
	"github.com/gorilla/mux"
Lukas Böhm's avatar
Lukas Böhm committed
10
	"github.com/rs/cors"
Lukas Böhm's avatar
Lukas Böhm committed
11
	"gorm.io/gorm"
Lukas Böhm's avatar
Lukas Böhm committed
12
	"io"
Lukas Böhm's avatar
Lukas Böhm committed
13
14
15
16
17
18
19
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

Lukas Böhm's avatar
Lukas Böhm committed
20
21
22
23
24
25
26
27
28
29
30
31
32
type HTTPError struct {
	Error   error
	Message string
	Code    int
}

type endpointREST func(http.ResponseWriter, *http.Request) *HTTPError

func (fn endpointREST) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *HTTPError, not os.Error.
		if e.Code == 401 {
			w.Header().Set("WWW-Authenticate", `Basic realm="Please enter the password"`)
		}
Lukas Böhm's avatar
Lukas Böhm committed
33
		http.Error(w, e.Message + " - " + e.Error.Error(), e.Code)
Lukas Böhm's avatar
Lukas Böhm committed
34
35
	}
}
Lukas Böhm's avatar
Lukas Böhm committed
36
37
38
39

/////////////////////////////////
//////////// routes /////////////
/////////////////////////////////
Lukas Böhm's avatar
Lukas Böhm committed
40
func AllShares(w http.ResponseWriter, _ *http.Request) *HTTPError {
Lukas Böhm's avatar
a    
Lukas Böhm committed
41
	fmt.Println("AllShares")
Lukas Böhm's avatar
Lukas Böhm committed
42
43
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
44
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
45
	}
Lukas Böhm's avatar
Lukas Böhm committed
46
	var shares []Share
Lukas Böhm's avatar
Lukas Böhm committed
47
	err = db.Where("is_public = ? AND is_temporary = 0", 1).Find(&shares).Error
Lukas Böhm's avatar
Lukas Böhm committed
48
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
49
		return &HTTPError{err, "Can't fetch data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
50
	}
Lukas Böhm's avatar
Lukas Böhm committed
51
	return SendJSON(w, shares)
Lukas Böhm's avatar
Lukas Böhm committed
52
53
}

54

Lukas Böhm's avatar
Lukas Böhm committed
55
func GetShare(w http.ResponseWriter, r *http.Request) *HTTPError {
Lukas Böhm's avatar
Lukas Böhm committed
56
	fmt.Println("Get Share")
Lukas Böhm's avatar
Lukas Böhm committed
57
	vars := mux.Vars(r)
Lukas Böhm's avatar
Lukas Böhm committed
58
59
	shareID, err := uuid.Parse(vars["id"])
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
60
		return &HTTPError{ err, "invalid URL param", 400 }
Lukas Böhm's avatar
Lukas Böhm committed
61
	}
Lukas Böhm's avatar
Lukas Böhm committed
62

Lukas Böhm's avatar
Lukas Böhm committed
63
64
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
65
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
66
	}
Lukas Böhm's avatar
Lukas Böhm committed
67

Lukas Böhm's avatar
Lukas Böhm committed
68
	var share Share
Lukas Böhm's avatar
Lukas Böhm committed
69
	err = db.Preload("Attachments").Where("ID = ?", shareID).First(&share).Error
Lukas Böhm's avatar
Lukas Böhm committed
70
	if errors.Is(err, gorm.ErrRecordNotFound) {
Lukas Böhm's avatar
Lukas Böhm committed
71
		return &HTTPError{err, "Record not found", 404}
Lukas Böhm's avatar
Lukas Böhm committed
72
73
	}
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
74
		return &HTTPError{err, "Can't fetch data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
75
76
	}
	if share.IsTemporary == true {
Lukas Böhm's avatar
Lukas Böhm committed
77
		return &HTTPError{err, "Share is not finalized", 403}
Lukas Böhm's avatar
Lukas Böhm committed
78
	}
Lukas Böhm's avatar
Lukas Böhm committed
79

80
	// auth
Lukas Böhm's avatar
Lukas Böhm committed
81
82
83
84
85
86
87
88
	if share.Password != "" {
		sid, pass, _ := r.BasicAuth()
		if sid != share.ID.String() {
			return &HTTPError{err, "wrong username", 401}
		}
		if pass != share.Password {
			return &HTTPError{err, "wrong password", 401}
		}
Lukas Böhm's avatar
Lukas Böhm committed
89
90
	}

Lukas Böhm's avatar
Lukas Böhm committed
91
	return SendJSON(w, share)
Lukas Böhm's avatar
Lukas Böhm committed
92
93
}

94

Lukas Böhm's avatar
Lukas Böhm committed
95
func DownloadFile(w http.ResponseWriter, r *http.Request) *HTTPError {
Lukas Böhm's avatar
Lukas Böhm committed
96
97
	fmt.Println("Download file")

Lukas Böhm's avatar
fix api    
Lukas Böhm committed
98
	vars := mux.Vars(r)
Lukas Böhm's avatar
Lukas Böhm committed
99
100
	shareID, err := uuid.Parse(vars["id"])
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
101
		return &HTTPError{ err, "invalid URL param", 400 }
Lukas Böhm's avatar
Lukas Böhm committed
102
	}
Lukas Böhm's avatar
Lukas Böhm committed
103
	attID, err := uuid.Parse(vars["att"])
Lukas Böhm's avatar
Lukas Böhm committed
104
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
105
		return &HTTPError{ err, "invalid URL param", 400 }
Lukas Böhm's avatar
Lukas Böhm committed
106
107
	}

Lukas Böhm's avatar
Lukas Böhm committed
108
109
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
110
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
111
	}
Lukas Böhm's avatar
Lukas Böhm committed
112
113

	var att Attachment
Lukas Böhm's avatar
Lukas Böhm committed
114
	err = db.Where("id = ?", attID.String()).First(&att).Error
Lukas Böhm's avatar
Lukas Böhm committed
115
	if errors.Is(err, gorm.ErrRecordNotFound) {
Lukas Böhm's avatar
Lukas Böhm committed
116
		return &HTTPError{err, "Record not found", 404}
Lukas Böhm's avatar
Lukas Böhm committed
117
118
	}
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
119
		return &HTTPError{err, "Can't fetch data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
120
	}
Lukas Böhm's avatar
Lukas Böhm committed
121

Lukas Böhm's avatar
Lukas Böhm committed
122
	var share Share
Lukas Böhm's avatar
Lukas Böhm committed
123
	err = db.Where("id = ?", att.ShareID.String()).First(&share).Error
Lukas Böhm's avatar
Lukas Böhm committed
124
125
126
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return &HTTPError{err, "Record not found", 404}
	}
Lukas Böhm's avatar
Lukas Böhm committed
127
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
128
		return &HTTPError{err, "Can't fetch data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
129
130
	}

131
	// auth
Lukas Böhm's avatar
Lukas Böhm committed
132
133
134
135
136
137
138
139
	if share.Password != "" {
		sid, pass, _ := r.BasicAuth()
		if sid != share.ID.String() {
			return &HTTPError{err, "wrong username", 401}
		}
		if pass != share.Password {
			return &HTTPError{err, "wrong password", 401}
		}
140
	}
Lukas Böhm's avatar
Lukas Böhm committed
141

Lukas Böhm's avatar
Lukas Böhm committed
142
	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", att.Filename))
Lukas Böhm's avatar
Lukas Böhm committed
143
	http.ServeFile(w, r, filepath.Join(config.mediaDir, "data", shareID.String(), attID.String()))
Lukas Böhm's avatar
Lukas Böhm committed
144
	return nil
Lukas Böhm's avatar
fix api    
Lukas Böhm committed
145
146
147
}


Lukas Böhm's avatar
Lukas Böhm committed
148
func OpenShare(w http.ResponseWriter, r *http.Request) *HTTPError {
149
	fmt.Println("OpenShare")
Lukas Böhm's avatar
Lukas Böhm committed
150

Lukas Böhm's avatar
Lukas Böhm committed
151
152
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
153
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
154
	}
Lukas Böhm's avatar
Lukas Böhm committed
155

Lukas Böhm's avatar
Lukas Böhm committed
156
	// parse body
157
158
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
159
		return &HTTPError{err, "Request does not contain a valid body", 400}
160
	}
Lukas Böhm's avatar
Lukas Böhm committed
161
	var newShare Share
Lukas Böhm's avatar
Lukas Böhm committed
162
163
	err = json.Unmarshal(reqBody, &newShare)
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
164
		return &HTTPError{err, "Can't parse body", 400}
Lukas Böhm's avatar
Lukas Böhm committed
165
	}
Lukas Böhm's avatar
Lukas Böhm committed
166
	newShare.Attachments = nil // dont want attachments yet
Lukas Böhm's avatar
Lukas Böhm committed
167

Lukas Böhm's avatar
Lukas Böhm committed
168
	// create temporary db entrie
Lukas Böhm's avatar
Lukas Böhm committed
169
	newShare.IsTemporary = true
170
	err = db.Create(&newShare).Error
Lukas Böhm's avatar
Lukas Böhm committed
171
	if err != nil {
172
		return &HTTPError{err, "Can't create data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
173
	}
Lukas Böhm's avatar
Lukas Böhm committed
174

Lukas Böhm's avatar
Lukas Böhm committed
175
	return SendJSON(w, newShare)
Lukas Böhm's avatar
Lukas Böhm committed
176
177
}

178

Lukas Böhm's avatar
Lukas Böhm committed
179
func CloseShare(w http.ResponseWriter, r *http.Request) *HTTPError {
180
	fmt.Println("CloseShare")
Lukas Böhm's avatar
Lukas Böhm committed
181

Lukas Böhm's avatar
Lukas Böhm committed
182
	vars := mux.Vars(r)
Lukas Böhm's avatar
Lukas Böhm committed
183
184
	shareID, err := uuid.Parse(vars["id"])
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
185
		return &HTTPError{ err, "invalid URL param", 400 }
Lukas Böhm's avatar
Lukas Böhm committed
186
187
	}

Lukas Böhm's avatar
Lukas Böhm committed
188
189
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
190
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
191
	}
Lukas Böhm's avatar
Lukas Böhm committed
192

Lukas Böhm's avatar
Lukas Böhm committed
193
194
	// get stuff
	var share Share
Lukas Böhm's avatar
Lukas Böhm committed
195
	err = db.Where("id = ?", shareID.String()).First(&share).Error
Lukas Böhm's avatar
Lukas Böhm committed
196
197
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return &HTTPError{err, "Record not found", 404}
Lukas Böhm's avatar
Lukas Böhm committed
198
	}
Lukas Böhm's avatar
Lukas Böhm committed
199
200
	if err != nil {
		return &HTTPError{err, "Can't fetch data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
201
202
	}

Lukas Böhm's avatar
Lukas Böhm committed
203
	// move files to permanent location
Lukas Böhm's avatar
Lukas Böhm committed
204
205
	oldPath := filepath.Join(config.mediaDir, "temp", shareID.String())
	newPath := filepath.Join(config.mediaDir, "data", shareID.String())
Lukas Böhm's avatar
Lukas Böhm committed
206
207
	err = os.Rename(oldPath, newPath)
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
208
		return &HTTPError{err, "Can't move directory", 500}
Lukas Böhm's avatar
Lukas Böhm committed
209
	}
Lukas Böhm's avatar
Lukas Böhm committed
210

Lukas Böhm's avatar
Lukas Böhm committed
211
212
	// set stuff permanent
	share.IsTemporary = false
Lukas Böhm's avatar
Lukas Böhm committed
213
214
	err = db.Save(&share).Error
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
215
		return &HTTPError{err, "Can't edit data", 500}
Lukas Böhm's avatar
Lukas Böhm committed
216
	}
Lukas Böhm's avatar
Lukas Böhm committed
217

Lukas Böhm's avatar
Lukas Böhm committed
218
	return SendJSON(w, share)
Lukas Böhm's avatar
Lukas Böhm committed
219
}
Lukas Böhm's avatar
Lukas Böhm committed
220

Lukas Böhm's avatar
Lukas Böhm committed
221

Lukas Böhm's avatar
Lukas Böhm committed
222
func UploadAttachment(w http.ResponseWriter, r *http.Request) *HTTPError {
223
	fmt.Println("UploadTest")
Lukas Böhm's avatar
Lukas Böhm committed
224

Lukas Böhm's avatar
Lukas Böhm committed
225
	vars := mux.Vars(r)
Lukas Böhm's avatar
Lukas Böhm committed
226
227
	shareID, err := uuid.Parse(vars["id"])
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
228
		return &HTTPError{ err, "invalid URL param", 400 }
Lukas Böhm's avatar
Lukas Böhm committed
229
230
	}

Lukas Böhm's avatar
Lukas Böhm committed
231
232
	db, err := GetDatabase()
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
233
		return &HTTPError{err, "Can't get database", 500}
Lukas Böhm's avatar
Lukas Böhm committed
234
	}
Lukas Böhm's avatar
Lukas Böhm committed
235

Lukas Böhm's avatar
Lukas Böhm committed
236
237
238
239
240
241
242
243
244
245
	// get share
	var share Share
	err = db.Where("id = ?", shareID.String()).First(&share).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return &HTTPError{err, "Record not found", 404}
	}
	if err != nil {
		return &HTTPError{err, "Can't fetch data", 500}
	}
	if share.IsTemporary != true {
Lukas Böhm's avatar
Lukas Böhm committed
246
		return &HTTPError{ err, "Can't upload to finalized Shares.",  403 }
Lukas Böhm's avatar
Lukas Böhm committed
247
248
	}

Lukas Böhm's avatar
Lukas Böhm committed
249
	// Parse file from body
Lukas Böhm's avatar
Lukas Böhm committed
250
	err = r.ParseMultipartForm(config.chunkSize)  // Maximum 10 MB in RAM
Lukas Böhm's avatar
Lukas Böhm committed
251
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
252
		return &HTTPError{err, "Request does not contain a valid body (parsing form)", 400}
Lukas Böhm's avatar
Lukas Böhm committed
253
	}
Lukas Böhm's avatar
Lukas Böhm committed
254
	file, handler, err := r.FormFile("file")
255
	if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
256
		return &HTTPError{err, "Request does not contain a valid body (parsing file)", 400}
257
	}
Lukas Böhm's avatar
Lukas Böhm committed
258
	defer file.Close() // TODO fehlerbehandlung
Lukas Böhm's avatar
Lukas Böhm committed
259
260

	var att Attachment
Lukas Böhm's avatar
Lukas Böhm committed
261
262
263
	{
		// add db entry TODO fehlerbehandlung für die ganze transaction
		db.Begin()
Lukas Böhm's avatar
Lukas Böhm committed
264
		sid, err := uuid.Parse(shareID.String())
Lukas Böhm's avatar
Lukas Böhm committed
265
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
266
			return &HTTPError{ err, "foreign key shareID not valid", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
267
268
269
270
271
272
273
274
275
		}
		att.ShareID = sid
		att.Filename = handler.Filename
		att.Filesize = handler.Size
		db.Create(&att)

		// save file
		fileBytes, err := ioutil.ReadAll(file)
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
276
			return &HTTPError{ err, "cant read file", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
277
		}
Lukas Böhm's avatar
Lukas Böhm committed
278
		err = ioutil.WriteFile(filepath.Join(config.mediaDir, "temp", sid.String(), att.ID.String()), fileBytes, os.ModePerm)
Lukas Böhm's avatar
Lukas Böhm committed
279
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
280
			return &HTTPError{ err, "cant save file", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
281
282
283
			db.Rollback()
		}
		db.Commit()
Lukas Böhm's avatar
Lukas Böhm committed
284
285
	}

Lukas Böhm's avatar
Lukas Böhm committed
286
	return SendJSON(w, att)
Lukas Böhm's avatar
Lukas Böhm committed
287
288
}

Lukas Böhm's avatar
Lukas Böhm committed
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
func DownloadZip(w http.ResponseWriter, r *http.Request) *HTTPError {
	fmt.Println("DownloadZip")

	vars := mux.Vars(r)
	shareID, err := uuid.Parse(vars["id"])
	if err != nil {
		return &HTTPError{ err, "invalid URL param", 400 }
	}

	share, er := RetrieveShare(shareID, true)
	if er != nil {
		return er
	}

	zipWriter := zip.NewWriter(w)
	for _, file := range share.Attachments {
		filePath := filepath.Join(config.mediaDir, "data", file.ShareID.String(), file.ID.String())
Lukas Böhm's avatar
Lukas Böhm committed
306
307
308
309
310
311
312
313

		fileToZip, err := os.Open(filePath)
		if err != nil {
			return &HTTPError{ err, "error opening file", 500}
		}
		defer fileToZip.Close()

		info, err := fileToZip.Stat()
Lukas Böhm's avatar
Lukas Böhm committed
314
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
315
			return &HTTPError{ err, "error getting file info", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
316
317
		}

Lukas Böhm's avatar
Lukas Böhm committed
318
		header, err := zip.FileInfoHeader(info)
Lukas Böhm's avatar
Lukas Böhm committed
319
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
320
			return &HTTPError{ err, "error creating file header", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
321
		}
Lukas Böhm's avatar
Lukas Böhm committed
322
323
324
325
326

		header.Name = file.Filename
		header.Method = zip.Deflate

		writer, err := zipWriter.CreateHeader(header)
Lukas Böhm's avatar
Lukas Böhm committed
327
		if err != nil {
Lukas Böhm's avatar
Lukas Böhm committed
328
			return &HTTPError{ err, "error creating header", 500 }
Lukas Böhm's avatar
Lukas Böhm committed
329
		}
Lukas Böhm's avatar
Lukas Böhm committed
330
		_, err = io.Copy(writer, fileToZip)
Lukas Böhm's avatar
Lukas Böhm committed
331
332
333
334
335
336
337
338
339
	}
	err = zipWriter.Close()
	if err != nil {
		return &HTTPError{ err, "error when closing zip", 500 }
	}

	return nil
}

Lukas Böhm's avatar
Lukas Böhm committed
340
341
342
/////////////////////////////////
////////// functions ////////////
/////////////////////////////////
Lukas Böhm's avatar
Lukas Böhm committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
func RetrieveShare(shareID uuid.UUID, withAtt bool) (*Share, *HTTPError) {
	db, err := GetDatabase()
	if err != nil {
		return nil, &HTTPError{err, "Can't get database", 500}
	}

	var share Share
	if withAtt == true {
		err = db.Preload("Attachments").Where("ID = ?", shareID).First(&share).Error
	}else {
		err = db.Where("ID = ?", shareID).First(&share).Error
	}

	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, &HTTPError{err, "Record not found", 404}
	}
	if err != nil {
		return nil, &HTTPError{err, "Can't fetch data", 500}
	}
	if share.IsTemporary == true {
		return nil, &HTTPError{err, "Share is not finalized", 403}
	}
	return &share, nil
}

Lukas Böhm's avatar
Lukas Böhm committed
368
func ConfigureRoutes() {
Lukas Böhm's avatar
Lukas Böhm committed
369
	router := mux.NewRouter().StrictSlash(true)
370
	handler := cors.Default().Handler(router)
Lukas Böhm's avatar
Lukas Böhm committed
371

Lukas Böhm's avatar
Lukas Böhm committed
372
373
	router.Handle("/shares", endpointREST(AllShares)).Methods("GET")
	router.Handle("/shares", endpointREST(OpenShare)).Methods("POST")
Lukas Böhm's avatar
Lukas Böhm committed
374

Lukas Böhm's avatar
Lukas Böhm committed
375
376
	router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
	router.Handle("/share/{id}", endpointREST(CloseShare)).Methods("POST")
Lukas Böhm's avatar
Lukas Böhm committed
377

Lukas Böhm's avatar
Lukas Böhm committed
378
	router.Handle("/share/{id}/attachments", endpointREST(UploadAttachment)).Methods("POST")
Lukas Böhm's avatar
Lukas Böhm committed
379

Lukas Böhm's avatar
Lukas Böhm committed
380
	router.Handle("/share/{id}/attachment/{att}", endpointREST(DownloadFile)).Methods("GET")
Lukas Böhm's avatar
Lukas Böhm committed
381
	router.Handle("/share/{id}/zip", endpointREST(DownloadZip)).Methods("GET")
Lukas Böhm's avatar
Lukas Böhm committed
382

Lukas Böhm's avatar
Lukas Böhm committed
383
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", config.port), handler))
Lukas Böhm's avatar
Lukas Böhm committed
384
385
}

Lukas Böhm's avatar
Lukas Böhm committed
386
func SendJSON(w http.ResponseWriter, res interface{}) *HTTPError {
Lukas Böhm's avatar
Lukas Böhm committed
387
	w.Header().Set("Content-Type", "application/json")
Lukas Böhm's avatar
Lukas Böhm committed
388
	err := json.NewEncoder(w).Encode(res)
389
390
391
392
	if err != nil {
		return &HTTPError{err, "Can't encode data", 500}
	}
	return nil
Lukas Böhm's avatar
Lukas Böhm committed
393
}