Moritz Schmidt 7 tahun lalu
melakukan
4d9d1a3d5c
12 mengubah file dengan 489 tambahan dan 0 penghapusan
  1. 24 0
      Dockerfile
  2. 3 0
      README.md
  3. 64 0
      docker-compose.yml
  4. 27 0
      docker/id_rsa
  5. 1 0
      docker/id_rsa.pub
  6. 1 0
      docker/known_hosts
  7. 8 0
      docker/ssh-config
  8. 105 0
      main.go
  9. 19 0
      module/config.go
  10. 160 0
      module/errors.go
  11. 55 0
      module/repository.go
  12. 22 0
      module/route.go

+ 24 - 0
Dockerfile

@@ -0,0 +1,24 @@
+FROM golang:1.10.0-alpine
+
+WORKDIR /go/src/app
+COPY . .
+# COPY . /usr/local/go/src/git.mmnx.de/Moe/webOffice_backend
+
+RUN apk add --no-cache git openssh-client && mkdir -p /root/.ssh && cp docker/ssh-config /root/.ssh/config && git config --global url."gogs@git.mmnx.de:".insteadOf "https://git.mmnx.de/"
+
+RUN wget https://raw.githubusercontent.com/docker-library/golang/master/go-wrapper && chmod +x go-wrapper
+
+# "go get -d -v ./..."
+RUN ./go-wrapper download
+
+# "go install -v ./..."
+RUN ./go-wrapper install
+
+# FOR DEV
+RUN go get github.com/githubnemo/CompileDaemon && go install github.com/githubnemo/CompileDaemon
+
+# FOR DEV
+CMD ["CompileDaemon", "-command=./app", "-graceful-kill=true"]
+
+# PROD
+# CMD ["./go-wrapper", "run"] # ["app"]

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# cdn
+# CDN management (web-)service
+

+ 64 - 0
docker-compose.yml

@@ -0,0 +1,64 @@
+version: '3'
+
+services:
+  cdn-db:
+    container_name: cdn-db
+    image: mariadb:latest
+    restart: always
+    volumes:
+      - cdn-db:/var/lib/mysql
+      # - "./c1nawik.sql:/docker-entrypoint-initdb.d/c1nawik.sql"
+    networks:
+      - cdn
+    ports:
+      - 3306:3306
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_DATABASE: cdn
+      MYSQL_USER: cdn
+      MYSQL_PASSWORD: cdnpw
+
+  cdn-web:
+    container_name: cdn-web
+    build:
+      context: .
+    depends_on:
+      - cdn-db
+    restart: always
+    volumes:
+      - "./:/go/src/app"
+      - "./:/usr/local/go/src/git.mmnx.de/mmnx/cdn"
+    networks:
+      - cdn
+      - proxy-nginx
+    ports:
+      - "420126:420126"
+    environment:
+      DB_HOST: db:3306
+      DB_USER: cdn
+      DB_PASSWORD: cdn
+      VIRTUAL_HOST: cdn
+
+# weboffice:
+#      container_name: weboffice
+#      build:
+#        context: .
+#      depends_on:
+#        - db
+#      ports:
+#        - "3000:3000"
+#      restart: always
+#      volumes:
+#        - "./:/go/src/app"
+#        - "./:/usr/local/go/src/git.mmnx.de/Moe/webOffice_backend"
+#      networks:
+#        - weboffice
+
+
+volumes:
+  cdn-db:
+
+networks:
+  proxy-nginx:
+    external: true
+  cdn:

+ 27 - 0
docker/id_rsa

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA2YRfMfiqQ4W00iCvfjB0fq73qnLw18ekrq5B2PvRMhujcifA
+NPCC/lsJ9K1rRsDgdJhC2StkJPf9+jCyyTUn7kx4QNxbkzaCj2qx1yd0IRv3S99p
+rOShpGGBU+90YaWZP85gVJr8hGm72ncrumcbJVDt9s84lLu9D6btMa3WZtMGZ8ge
+M8Yg+K4EIQEGM79zJPdzoaaJyDVK/LE6YBvsJW2qfubPR7aQkK6mrCtPf+zWkym/
+yM9BQzs2v2/OOiQnmIzaapnzj1iqHwNvGt+iBoXfsY6a82xmDBAuGsRierM10Avl
+RW1IQnzyAeH5LX+gxA9VxIxwW6oaB9xnRsGTEwIDAQABAoIBACGZI8cpPMt/PKX0
+bIcWXvgueIz4giEiclqVChDFHNQblsyMPVEwEVvOniQQIXDlXAZGepTm8eoP8EgT
++5m+vzM81ppWL0L2+YXDmkK8kmCG3FT45m7RRfKJKRvY8eYfWSOU8iY/Zl/hoSWm
+oLeDM5s4+/jSm0/GC/b99sdkrahmCQ4GEYWuLuLUaFbc4Dc78Y3gNg4kZMQJXt86
+4xmKeOIDrTdXJOve4vuNKi7cM/MFIFMlaGTvClNXBnYWNh2ViEzU++IoamcioTCx
+rXGLzPE0/4hCg64chmG7qeUD2CfyEMz9yUKiEAC25dh6TNto0Px/VV3fUvMKzhhE
+36QZI0ECgYEA/j3yyKVpO9BAOzihIjAWvl4NHEnfmWNGHAIw0LHxBZqYTDiJjJLJ
+ivtVuwsi8yCkTQeLsN2dHo8dJ9w40nr6nRA5AXgPppY0dDcPPlFLJfkm4pKwHx8f
+oJUXgCbB5GBugzQz2wyZIUrwogslDFml2HR6CKu4iB5dkR1WMhPmf+ECgYEA2wVq
+BPdyJhmAPc/JzH4nlg41+28VCJw2ZkUvmzXneDHAbFXIDJaIjXMnKJMjpwT/+X4e
+sIGnccXCnkxf3ti9AQCEyewVgIx5WagNUwXjUrraz13Ja2akTygRmTlvOxEiKM7P
+w5rKOQ3heKtmBhk5/zSKjoohS74AKKOj379+QXMCgYEAxZtsqts2EdFpOx5mz2XF
+J61+TzBz5IquoXJqcqr7hLmGCigfcO1eTPHQzpJ3VbVXPq/QuWVmi6v8o1Cdsar4
+Qz+qryOcNoSkvHdXF76jxB4FRTFyRp4wPOXk4Cw/o7v36W6Xp3gBRUk0Ax7aauD6
+8GSHvVmLEfFYFERTDJU/IMECgYBc2kbAJEd4b4c2rUjYMQOHUi8AkW9RXyiQ4vPz
+0T54bl8tjpBpBciObytCwF51CGGc3CMWoWDjkvuXMLnUN/6o1hd1o2jaPrOTlvNP
+J0fgYucw0on5xmISTOJpGzhUyka5FjRusMjat0/HYKK4Iym7UAMNuvGysrJOiFGJ
+Ih3ckQKBgD+Aow/YaU/DXdbvY72JmjnWewxKw1YTUM41nw0xkCpf4xZY5LE+fx9O
+8MsRLrA6KZm6c7rkUzePS/2gGcoUyXZeGDOnYqPA2wTY/D3g7aiGUaTa4xsw15/6
+06hTFT2C7E/NQMGpaTJjs8BsnEq1kAw/gfh+HIYSRU7+cXBoQCg8
+-----END RSA PRIVATE KEY-----

+ 1 - 0
docker/id_rsa.pub

@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZhF8x+KpDhbTSIK9+MHR+rveqcvDXx6SurkHY+9EyG6NyJ8A08IL+Wwn0rWtGwOB0mELZK2Qk9/36MLLJNSfuTHhA3FuTNoKParHXJ3QhG/dL32ms5KGkYYFT73RhpZk/zmBUmvyEabvadyu6ZxslUO32zziUu70Ppu0xrdZm0wZnyB4zxiD4rgQhAQYzv3Mk93OhponINUr8sTpgG+wlbap+5s9HtpCQrqasK09/7NaTKb/Iz0FDOza/b846JCeYjNpqmfOPWKofA28a36IGhd+xjprzbGYMEC4axGJ6szXQC+VFbUhCfPIB4fktf6DED1XEjHBbqhoH3GdGwZMT m@hotdog

+ 1 - 0
docker/known_hosts

@@ -0,0 +1 @@
+git.mmnx.de,79.143.187.141 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJPwL0Aa1+Dedd/YKBVekoK7SOfuyw1oZ0Q/5wEGaMfYry2uX2TZX/n2IBoTVo+m6Nn0IdSyIzFyS87TRExOICc=

+ 8 - 0
docker/ssh-config

@@ -0,0 +1,8 @@
+
+Host git.mmnx.de
+  User gogs
+  Port 22
+  IdentityFile    /go/src/app/docker/id_rsa
+  IdentitiesOnly yes
+  UserKnownHostsFile /go/src/app/docker/known_hosts
+  StrictHostKeyChecking no

+ 105 - 0
main.go

@@ -0,0 +1,105 @@
+package main
+
+import (
+	"encoding/json"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"strings"
+	"time"
+
+	// "git.mmnx.de/Moe/webOffice_backend/contacts"
+	// "git.mmnx.de/Moe/webOffice_backend/module"
+	"git.mmnx.de/mmnx/cdn/module"
+	
+	"github.com/gorilla/mux"
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/mysql"
+	"github.com/rs/cors"
+	"github.com/urfave/negroni"
+)
+
+func connectDB(host string) *gorm.DB {
+	time.Sleep(250 * time.Millisecond) // let mysql startup
+	db, err := gorm.Open("mysql", "wo:wo@tcp("+host+")/weboffice?charset=utf8mb4&parseTime=True&loc=Local")
+	if err != nil {
+		if strings.HasPrefix(err.Error(), "default addr for network") {
+			log.Println("Reconnecting..")
+			return connectDB(host)
+		}
+
+		log.Println(err)
+		return nil
+	}
+
+	log.Println("Apparently connected")
+	return db
+}
+
+func main() {
+	log.SetFlags(log.LstdFlags | log.Lshortfile) // get line numberz
+
+	c := cors.New(cors.Options{ // allow ng dev server access
+		AllowedOrigins: []string{"http://localhost:4200"},
+		AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
+	})
+
+	// lookup docker db ip
+	names, err := net.LookupHost("cdn-db")
+	if err != nil {
+		log.Println("Failed to get DB host: ", err)
+		return // TODO retry
+	}
+
+	if len(names) < 1 {
+		log.Println("No hosts")
+		return
+	}
+
+	db := connectDB(names[0])
+	defer db.Close()
+
+	router := mux.NewRouter()
+	apiV1 := router.PathPrefix("/api/v1").Subrouter()
+
+	modConf := module.Config{apiV1, "/api/v1", db, true, log.New(os.Stdout, "[debug] ", log.LstdFlags|log.Lshortfile)} // debug
+	//modConf := module.Config{apiV1, "/api/v1", db, false, log.New(os.Stdout, "[log] ", log.LstdFlags|log.Lshortfile)} // production
+	module.Conf = modConf
+
+	// contacts.RegisterModule(&modConf)
+
+	module.NegroniRoute(apiV1, "/api/v1", "/test", "GET", HandlerFunc, AuthFunc)
+
+	n := negroni.Classic()
+	n.Use(module.NewHTTPStatusHandler())
+	n.Use(c)
+	n.UseHandler(router)
+	n.Run(":420126")
+
+}
+
+// AuthFunc ...
+func AuthFunc(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+	// TODO some kind of auth
+
+	next(w, r)
+}
+
+// HandlerFunc ...
+func HandlerFunc(w http.ResponseWriter, r *http.Request) {
+	encJSON, _ := json.Marshal("wörks") // err
+	// errs.HandleError(errs.ErrEncodingJSON, err)
+	//
+	// // Error handling debug
+	// err = errors.New("ayyyyy")
+	// errs.HandleError(errs.ErrUnknown, err)
+
+	// names, err := net.LookupHost("weboffice-db")
+	// module.HandleError(module.ErrUnknown, err)
+	//
+	// spew.Dump(names)
+
+	w.Write(encJSON)
+	// log.Println(string(encJSON))
+}

+ 19 - 0
module/config.go

@@ -0,0 +1,19 @@
+package module
+
+import (
+	"log"
+
+	"github.com/gorilla/mux"
+	"github.com/jinzhu/gorm"
+)
+
+// Conf is the module configuration accessible globally
+var Conf Config
+
+type Config struct {
+	Router   *mux.Router
+	BasePath string
+	DB       *gorm.DB
+	Debug    bool
+	Logger   *log.Logger
+}

+ 160 - 0
module/errors.go

@@ -0,0 +1,160 @@
+package module
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"runtime"
+)
+
+// Err... are errors used globally
+var (
+	ErrUnknown = errors.New("Unknown error")
+	// ErrRetrievingImage            = errors.New("Error retrieving image")
+	// ErrInvalidURL                 = errors.New("Passed URL is not valid")
+	// ErrDecodingImage              = errors.New("Error decoding image")
+	// ErrWidthNoInteger             = errors.New("Error parsing width parameter as integer")
+	// ErrHeightNoInteger            = errors.New("Error parsing height parameter as integer")
+	// ErrAspectRatioWidthNoInteger  = errors.New("Error parsing aspect ratio width parameter as integer")
+	// ErrAspectRatioHeightNoInteger = errors.New("Error parsing aspect ratio height parameter as integer")
+	ErrReadingRequestBody = errors.New("Error reading request body")
+	ErrDecodingJSON       = errors.New("Error decoding JSON")
+	// ErrDecodingBase64             = errors.New("Error decoding base64 encoded image")
+	// ErrReadingCroppedImage        = errors.New("Error reading cropped image")
+	// ErrCreatingThumbnail          = errors.New("Error creating thumbnail")
+	// ErrDecodingImageInternal      = errors.New("Error decoding image")
+	// ErrEncodingImage              = errors.New("Error encoding image")
+	ErrSendingResponse    = errors.New("Error sending response")
+	ErrEncodingJSON       = errors.New("Error encoding JSON")
+	ErrDatabase           = errors.New("Database error")
+	ErrRetrievingTestData = errors.New("Error receiving Testdata")
+	ErrParsingInt         = errors.New("Error parsing string to int")
+)
+
+// StatusCodeMapper maps errors to HTTP Status codes
+var StatusCodeMapper = map[error]int{
+	ErrUnknown: http.StatusInternalServerError,
+	// ErrRetrievingImage:            http.StatusBadRequest,
+	// ErrInvalidURL:                 http.StatusBadRequest,
+	// ErrDecodingImage:              http.StatusBadRequest,
+	// ErrWidthNoInteger:             http.StatusBadRequest,
+	// ErrHeightNoInteger:            http.StatusBadRequest,
+	// ErrAspectRatioWidthNoInteger:  http.StatusBadRequest,
+	// ErrAspectRatioHeightNoInteger: http.StatusBadRequest,
+	ErrReadingRequestBody: http.StatusBadRequest,
+	ErrDecodingJSON:       http.StatusBadRequest,
+	// ErrDecodingBase64:             http.StatusBadRequest,
+	// ErrReadingCroppedImage:        http.StatusInternalServerError,
+	// ErrCreatingThumbnail:          http.StatusInternalServerError,
+	// ErrDecodingImageInternal:      http.StatusInternalServerError,
+	// ErrEncodingImage:              http.StatusInternalServerError,
+	ErrSendingResponse:    http.StatusInternalServerError,
+	ErrEncodingJSON:       http.StatusInternalServerError,
+	ErrDatabase:           http.StatusInternalServerError,
+	ErrRetrievingTestData: http.StatusBadGateway,
+	ErrParsingInt:         http.StatusInternalServerError,
+}
+
+// MetaError is two errors in one
+type MetaError struct {
+	outerError error
+	innerError error
+	httpStatus int
+}
+
+// NewMetaError returns a new MetaError and automatically selects the suited HTTP Status Code
+func NewMetaError(outerError error, innerError error) MetaError {
+	return MetaError{outerError, innerError, StatusCodeMapper[outerError]}
+}
+
+// NewMetaErrorWithStatus returns a new MetaError with manually selected HTTP Status Code
+func NewMetaErrorWithStatus(outerError error, innerError error, httpStatus int) MetaError {
+	return MetaError{outerError, innerError, httpStatus}
+}
+
+func (err MetaError) Error() string {
+	return err.outerError.Error() + ": " + err.innerError.Error()
+}
+
+type StatusError struct {
+	error
+	httpStatus int
+}
+
+func NewStatusError(err error, status int) StatusError {
+	return StatusError{err, status}
+}
+
+// HandleError will handle errors
+func HandleError(outerErr error, innerErr error) {
+	if innerErr != nil {
+
+		switch outerErr {
+		case ErrDatabase:
+			switch innerErr.Error() {
+			case "record not found":
+				panic(NewStatusError(innerErr, http.StatusNotFound))
+			}
+		}
+
+		panic(NewMetaError(outerErr, innerErr))
+	}
+}
+
+// HTTPStatusHandler ...
+type HTTPStatusHandler struct {
+	Debug bool // TODO implement
+}
+
+// NewHTTPStatusHandler ...
+func NewHTTPStatusHandler() *HTTPStatusHandler {
+	return &HTTPStatusHandler{
+		Debug: false,
+	}
+}
+
+// HTTPStatusRecovery catches errors and serves http statuses
+func (rec *HTTPStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+	defer func() {
+		if err := recover(); err != nil {
+			// get the stack traces and print it out
+			// stack := stack(0)
+			// log.Printf("PANIC: %s\n%s", err, stack)
+
+			// convert err to a string
+			var errMsg string
+			if e, ok := err.(error); ok {
+				errMsg = e.Error()
+			} else {
+				errMsg = fmt.Sprint(err)
+			}
+
+			// Logging
+			_, file, line, _ := runtime.Caller(4)
+			log.Printf("%s:%d: %s", file, line, errMsg)
+
+			errText, jErr := json.Marshal(map[string]string{"status": errMsg})
+			if jErr != nil {
+				log.Println(jErr)
+				// TODO handle error
+			}
+
+			if _, ok := err.(MetaError); ok {
+				http.Error(w, string(errText), err.(MetaError).httpStatus)
+			} else if _, ok = err.(StatusError); ok {
+				http.Error(w, string(errText), err.(StatusError).httpStatus)
+			} else {
+				http.Error(w, string(errText), http.StatusInternalServerError)
+			}
+		}
+	}()
+
+	next(w, r)
+}
+
+// Log logs
+func Log(text string) {
+	log.Println(text)
+}

+ 55 - 0
module/repository.go

@@ -0,0 +1,55 @@
+package module
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+)
+
+// helpers for repositorys
+
+// JSONEncode encodes an interface to json encoded byte array, formatted if debug is enabled
+func JSONEncode(v interface{}) ([]byte, error) {
+	var (
+		err     error
+		rawJSON []byte
+	)
+
+	if Conf.Debug {
+		rawJSON, err = json.MarshalIndent(v, "", " ") // debug output
+		if err != nil {
+			log.Println("MARSHAL FAILED")
+			log.Println(err)
+			return []byte(""), err
+		}
+	} else {
+		rawJSON, err = json.Marshal(v) // prod
+		if err != nil {
+			log.Println("MARSHAL FAILED")
+			log.Println(err)
+			return []byte(""), err
+		}
+	}
+
+	return rawJSON, nil
+}
+
+// JSONDecode decodes given JSON into passed interface object
+func JSONDecode(rawJSON []byte, v interface{}) error {
+	return json.Unmarshal(rawJSON, v)
+}
+
+// JSONReq decodes POST data in request body in passed interface
+func JSONReq(r *http.Request, v interface{}) {
+	json.NewDecoder(r.Body).Decode(v)
+}
+
+// JSONResp encodes passed interface and responses the request with interface JSON encoded
+func JSONResp(w http.ResponseWriter, v interface{}) {
+	enc := json.NewEncoder(w)
+	if Conf.Debug {
+		enc.SetIndent("", " ")
+	}
+
+	enc.Encode(v)
+}

+ 22 - 0
module/route.go

@@ -0,0 +1,22 @@
+package module
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/gorilla/mux"
+	"github.com/urfave/negroni"
+)
+
+// NegroniRoute helps with using middlewares, found on the interwebz; called by modules when registering APIs
+func NegroniRoute(m *mux.Router, base string, path string, pathTypes string, f func(http.ResponseWriter, *http.Request), mids ...func(http.ResponseWriter, *http.Request, http.HandlerFunc)) {
+	_routes := mux.NewRouter()
+	_routes.HandleFunc(base+path, f).Methods(strings.Split(pathTypes, " ")...)
+
+	_n := negroni.New()
+	for i := range mids {
+		_n.Use(negroni.HandlerFunc(mids[i]))
+	}
+	_n.UseHandler(_routes)
+	m.Handle(path, _n)
+}