makeless / makeless-go

[BETA] Makeless - SaaS Framework - Golang Implementation

Home Page:https://github.com/makeless

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

custom integration question

opened this issue · comments

Hi guys,

Hope you are all well !

I would like to use makeless to administrate users and token for a mini cdn service. The cdn is pretty straight forward as it is using gin + gin-cache contrib.

How can I wrap makeless with the following piece of code ?

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-contrib/gzip"
	"github.com/gin-gonic/gin"
	"github.com/gin-contrib/cache"
	"github.com/gin-contrib/cache/persistence"
)

func main() {

	cssRegex, err := regexp.Compile(cssRelativePath)
	if err != nil {
		log.Fatal(err)
	}

	router := gin.Default()
	store := persistence.NewInMemoryStore(time.Second)

	// CORS for https://foo.com and https://github.com origins, allowing:
	// - PUT and PATCH methods
	// - Origin header
	// - Credentials share
	// - Preflight requests cached for 12 hours
	router.Use(cors.New(cors.Config{
		AllowOrigins:  []string{"*"},
		AllowMethods:  []string{"GET"},
		AllowHeaders:  []string{"Origin"},
		ExposeHeaders: []string{"Content-Length"},
		MaxAge:        24 * time.Hour,
	})) // Access-Control-Allow-Origin

	router.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})))

	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
	})

	// Cached Page
	router.GET("/cache_ping", cache.CachePage(store, time.Minute, func(c *gin.Context) {
		c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
	}))

	router.GET("/", cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
		remoteUrl := c.Query("url")
		if remoteUrl == "" {
			c.Status(http.StatusServiceUnavailable)
			return
		}
		response, err := http.Get(remoteUrl)
		if err != nil || response.StatusCode != http.StatusOK {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		defer response.Body.Close()
		body, err := ioutil.ReadAll(response.Body)
		if err != nil {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		// https://github.com/gin-gonic/gin/issues/1222
		// c.Header("Cache-Control", "public, max-age=31536000")
		now := time.Now()
		cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
		c.Header("Expires", cacheExpire)
		c.Header("Cache-Control", "public, max-age=2592000")
		c.Header("ETag", getEtag(body))

		contentType := response.Header.Get("Content-Type")
		c.Data(http.StatusOK, contentType, body)
	}))

	router.Run(":8080")
}

func getEtag(content []byte) string {
	hasher := md5.New()
	hasher.Write(content)
	return hex.EncodeToString(hasher.Sum(nil))
}

The goal is to get/check a token per asset url and check the domain in order to authorize the caching.

For eg,
https://cdn.your-domain.com/api/v1/cache/?token=**domain-token**&url=https://www.some-domain.com/img/m/571-small_default.jpg

Thanks for any insights or inputs on that.

Cheers,
Luc Michalski

Hey @lucmichalski,

thank you for your question - thats pretty easy to do.

Will get back to this at monday (UTC) 👍

thanks :-)

Hey @lucmichalski,

everything is built up on interfaces to customize the most packages if needed. Mostly there is a basic package and some advanced packages like authenticator and authenticator-in-memory (to use the whole system in microservices for example), mailer and mailer-mailgun or event and event-redis which give you some kind of informations how to customize the packages

to customize the whole http part there is one simple method: makeless.SetRoute - with this method you can replace or create any existing http route

there is also a makeless.SetMail method - with this you can replace or create any existing mail template

There are some examples of that from a side project:

package main
 
import (
    "fmt"
    "os"
    "strings"
    "sync"
    "time"
 
    "gorm.io/driver/mysql"
 
    "github.com/makeless/makeless-go"
    "github.com/makeless/makeless-go-event-redis"
    "github.com/makeless/makeless-go-mailer-mailgun"
    "github.com/makeless/makeless-go/authenticator/basic"
    "github.com/makeless/makeless-go/config/basic"
    "github.com/makeless/makeless-go/database/basic"
    "github.com/makeless/makeless-go/event/basic"
    "github.com/makeless/makeless-go/http"
    "github.com/makeless/makeless-go/http/basic"
    "github.com/makeless/makeless-go/logger/basic"
    "github.com/makeless/makeless-go/mailer"
    "github.com/makeless/makeless-go/security/basic"
    "github.com/makeless/makeless-go/tls/basic"
 
    bpcache "github.com/bahn-preisalarm/api/cache"
)
 
func main() {
    // logger
    logger := new(makeless_go_logger_basic.Logger)
 
    // mailer
    mailer := &makeless_go_mailer_mailgun.Mailer{
        Handlers: make(map[string]func(data map[string]interface{}) (makeless_go_mailer.Mail, error)),
        ApiBase:  os.Getenv("MAILGUN_API_BASE"),
        Domain:   os.Getenv("MAILGUN_DOMAIN"),
        ApiKey:   os.Getenv("MAILGUN_API_KEY"),
        RWMutex:  new(sync.RWMutex),
    }
 
    // config
    config := &makeless_go_config_basic.Config{
        RWMutex: new(sync.RWMutex),
    }
 
    // database
    database := &makeless_go_database_basic.Database{
        Host:     os.Getenv("DB_HOST"),
        Database: os.Getenv("DB_NAME"),
        Port:     os.Getenv("DB_PORT"),
        Username: os.Getenv("DB_USER"),
        Password: os.Getenv("DB_PASS"),
        RWMutex:  new(sync.RWMutex),
    }
 
    // security
    security := &makeless_go_security_basic.Security{
        Database: database,
        RWMutex:  new(sync.RWMutex),
    }
 
    // bp cache
    bpCache := &bpcache.Cache{
        Addr:     fmt.Sprintf("%s:%s", os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_PORT")),
        Password: os.Getenv("REDIS_PASSWORD"),
        Db:       0,
        RWMutex:  new(sync.RWMutex),
    }
 
    if err := bpCache.Connect(); err != nil {
        logger.Fatal(err)
    }
 
    // event hub
    hub := &makeless_go_event_basic.Hub{
        List:    new(sync.Map),
        RWMutex: new(sync.RWMutex),
    }
 
    // event
    event := &makeless_go_event_redis.Event{
        Client: bpCache.GetClient(),
        BaseEvent: &makeless_go_event_basic.Event{
            Hub:     hub,
            Error:   make(chan error),
            RWMutex: new(sync.RWMutex),
        },
        RWMutex: new(sync.RWMutex),
    }
 
    // jwt authenticator
    authenticator := &makeless_go_authenticator_basic.Authenticator{
        Security:    security,
        Realm:       "auth",
        Key:         os.Getenv("JWT_KEY"),
        Timeout:     time.Hour,
        MaxRefresh:  time.Hour,
        IdentityKey: "id",
        RWMutex:     new(sync.RWMutex),
    }
 
    // tls
    tls := &makeless_go_tls_basic.Tls{
        CertPath: os.Getenv("CERT_PATH"),
        KeyPath:  os.Getenv("KEY_PATH"),
        RWMutex:  new(sync.RWMutex),
    }
 
    // router
    router := &makeless_go_http_basic.Router{
        RWMutex: new(sync.RWMutex),
    }
 
    // http
    http := &makeless_go_http_basic.Http{
        Router:        router,
        Handlers:      make(map[string]func(http makeless_go_http.Http) error),
        Logger:        logger,
        Event:         event,
        Authenticator: authenticator,
        Security:      security,
        Database:      database,
        Mailer:        mailer,
        Tls:           tls,
        Origins:       strings.Split(os.Getenv("ORIGINS"), ","),
        Headers:       []string{},
        Port:          os.Getenv("API_PORT"),
        Mode:          os.Getenv("API_MODE"),
        RWMutex:       new(sync.RWMutex),
    }
 
    // makeless
    makeless := &makeless_go.Makeless{
        Config:   config,
        Logger:   logger,
        Database: database,
        Mailer:   mailer,
        Http:     http,
        RWMutex:  new(sync.RWMutex),
    }
 
    if err := makeless.Init(mysql.Open(database.GetConnectionString()), os.Getenv("MAKELESS_CONFIG")); err != nil {
        makeless.GetLogger().Fatal(err)
    }
 
    bp := &BP{
        Makeless: makeless,
        Cache:    bpCache,
        Mailer:   mailer,
        RWMutex:  new(sync.RWMutex),
    }
 
    if err := bp.Init(); err != nil {
        logger.Fatal(err)
    }
 
    // run
    if err := makeless.Run(); err != nil {
        makeless.GetLogger().Fatal(err)
    }
}
func (bp *BP) testHandler(http makeless_go_http.Http) error {
    http.GetRouter().GetEngine().GET(
        "/api/test",
        http.GetAuthenticator().GetMiddleware().MiddlewareFunc(),
        http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()),
        bp.GetHttp().AnotherMiddlewareExample(),
        func(c *gin.Context) {
            http.Response(nil, "test")
        },
    )
 
    return nil
}
 
// use it like this 
 
makeless.SetRoute("testHandler", bp.testHandler) 

Hope this can help you somehow

Hi @loeffel-io ,

Thanks for this ! I am a little bit confused by all the package loaded,
can you provide a more related example or a full snippet ? plz

Cheers,
Luc Michalski

Hey @lucmichalski,

dont have that time actually but at the end its all about creating a struct for your acme like CDN.
Into this you put all of your acme related components (your store for example) and the final makeless struct

Then you just build up your handlers like: (pseudo code)

func (cdn *CDN) rootHandler(http makeless_go_http.Http) error {
    http.GetRouter().GetEngine().GET(
        "/",
        cdn.TokenMiddleware(), // your custom middleware to verify the token
        http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()), // use this middleware if you want to allow only email verified users or just delete it 
        gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})),  // use it here or just do it globally (think its better to use this only for selected routes
        cdn.cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
		remoteUrl := c.Query("url")
		if remoteUrl == "" {
			c.Status(http.StatusServiceUnavailable)
			return
		}
		response, err := http.Get(remoteUrl)
		if err != nil || response.StatusCode != http.StatusOK {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		defer response.Body.Close()
		body, err := ioutil.ReadAll(response.Body)
		if err != nil {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		// https://github.com/gin-gonic/gin/issues/1222
		// c.Header("Cache-Control", "public, max-age=31536000")
		now := time.Now()
		cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
		c.Header("Expires", cacheExpire)
		c.Header("Cache-Control", "public, max-age=2592000")
		c.Header("ETag", getEtag(body))

		contentType := response.Header.Get("Content-Type")
		c.Data(http.StatusOK, contentType, body)
    })
 
    return nil
}

Can i close this @lucmichalski?

Hi @loeffel-io,

Hope you are all well !

Sorry for the delay but I was busy with another project so now I can focus on makeless again. ^^

I tried to make an executable from the information above but I have an error with a dependency "bpcache" "github.com/bahn-preisalarm/api/cache".

Screenshot 2021-02-27 at 12 50 50

And i saw that you are member of the organisation https://github.com/bahn-preisalarm/, so maybe it was moved.

Is there any way to sort it out ? Do I need to use redis ?

Here is my main.go file:

package main

import (
    "fmt"
    "os"
    "strings"
    "sync"
    "time"

    "github.com/gin-contrib/cors"
    "github.com/gin-contrib/gzip"
    "github.com/gin-gonic/gin"
    "gorm.io/driver/mysql"

    "github.com/makeless/makeless-go"
    "github.com/makeless/makeless-go-event-redis"
    "github.com/makeless/makeless-go-mailer-mailgun"
    "github.com/makeless/makeless-go/authenticator/basic"
    "github.com/makeless/makeless-go/config/basic"
    "github.com/makeless/makeless-go/database/basic"
    "github.com/makeless/makeless-go/event/basic"
    "github.com/makeless/makeless-go/http"
    "github.com/makeless/makeless-go/http/basic"
    "github.com/makeless/makeless-go/logger/basic"
    "github.com/makeless/makeless-go/mailer"
    "github.com/makeless/makeless-go/security/basic"
    "github.com/makeless/makeless-go/tls/basic"

    bpcache "github.com/bahn-preisalarm/api/cache"
)

func main() {
    // logger
    logger := new(makeless_go_logger_basic.Logger)

    // mailer
    mailer := &makeless_go_mailer_mailgun.Mailer{
        Handlers: make(map[string]func(data map[string]interface{}) (makeless_go_mailer.Mail, error)),
        ApiBase:  os.Getenv("MAILGUN_API_BASE"),
        Domain:   os.Getenv("MAILGUN_DOMAIN"),
        ApiKey:   os.Getenv("MAILGUN_API_KEY"),
        RWMutex:  new(sync.RWMutex),
    }

    // config
    config := &makeless_go_config_basic.Config{
        RWMutex: new(sync.RWMutex),
    }

    // database
    database := &makeless_go_database_basic.Database{
        Host:     os.Getenv("DB_HOST"),
        Database: os.Getenv("DB_NAME"),
        Port:     os.Getenv("DB_PORT"),
        Username: os.Getenv("DB_USER"),
        Password: os.Getenv("DB_PASS"),
        RWMutex:  new(sync.RWMutex),
    }

    // security
    security := &makeless_go_security_basic.Security{
        Database: database,
        RWMutex:  new(sync.RWMutex),
    }

    // bp cache
    bpCache := &bpcache.Cache{
        Addr:     fmt.Sprintf("%s:%s", os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_PORT")),
        Password: os.Getenv("REDIS_PASSWORD"),
        Db:       0,
        RWMutex:  new(sync.RWMutex),
    }

    if err := bpCache.Connect(); err != nil {
        logger.Fatal(err)
    }

    // event hub
    hub := &makeless_go_event_basic.Hub{
        List:    new(sync.Map),
        RWMutex: new(sync.RWMutex),
    }

    // event
    event := &makeless_go_event_redis.Event{
        Client: bpCache.GetClient(),
        BaseEvent: &makeless_go_event_basic.Event{
            Hub:     hub,
            Error:   make(chan error),
            RWMutex: new(sync.RWMutex),
        },
        RWMutex: new(sync.RWMutex),
    }

    // jwt authenticator
    authenticator := &makeless_go_authenticator_basic.Authenticator{
        Security:    security,
        Realm:       "auth",
        Key:         os.Getenv("JWT_KEY"),
        Timeout:     time.Hour,
        MaxRefresh:  time.Hour,
        IdentityKey: "id",
        RWMutex:     new(sync.RWMutex),
    }

    // tls
    tls := &makeless_go_tls_basic.Tls{
        CertPath: os.Getenv("CERT_PATH"),
        KeyPath:  os.Getenv("KEY_PATH"),
        RWMutex:  new(sync.RWMutex),
    }

    // router
    router := &makeless_go_http_basic.Router{
        RWMutex: new(sync.RWMutex),
    }

    // http
    http := &makeless_go_http_basic.Http{
        Router:        router,
        Handlers:      make(map[string]func(http makeless_go_http.Http) error),
        Logger:        logger,
        Event:         event,
        Authenticator: authenticator,
        Security:      security,
        Database:      database,
        Mailer:        mailer,
        Tls:           tls,
        Origins:       strings.Split(os.Getenv("ORIGINS"), ","),
        Headers:       []string{},
        Port:          os.Getenv("API_PORT"),
        Mode:          os.Getenv("API_MODE"),
        RWMutex:       new(sync.RWMutex),
    }

    // makeless
    makeless := &makeless_go.Makeless{
        Config:   config,
        Logger:   logger,
        Database: database,
        Mailer:   mailer,
        Http:     http,
        RWMutex:  new(sync.RWMutex),
    }

    if err := makeless.Init(mysql.Open(database.GetConnectionString()), os.Getenv("MAKELESS_CONFIG")); err != nil {
        makeless.GetLogger().Fatal(err)
    }

    cdn := &CDN{
        Makeless: makeless,
        Cache:    bpCache,
        Mailer:   mailer,
        RWMutex:  new(sync.RWMutex),
    }

    if err := cdn.Init(); err != nil {
        logger.Fatal(err)
    }

    makeless.SetRoute("/", cdn.rootHandler)

    // run
    if err := makeless.Run(); err != nil {
        makeless.GetLogger().Fatal(err)
    }
}

func (cdn *CDN) rootHandler(http makeless_go_http.Http) error {
    http.GetRouter().GetEngine().GET(
        "/",
        cdn.TokenMiddleware(), // your custom middleware to verify the token
        http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()), // use this middleware if you want to allow only email verified users or just delete it
        gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})),       // use it here or just do it globally (think its better to use this only for selected routes
        cdn.cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
            remoteUrl := c.Query("url")
            if remoteUrl == "" {
                c.Status(http.StatusServiceUnavailable)
                return
            }
            response, err := http.Get(remoteUrl)
            if err != nil || response.StatusCode != http.StatusOK {
                c.Status(http.StatusServiceUnavailable)
                return
            }

            defer response.Body.Close()
            body, err := ioutil.ReadAll(response.Body)
            if err != nil {
                c.Status(http.StatusServiceUnavailable)
                return
            }

            // https://github.com/gin-gonic/gin/issues/1222
            // c.Header("Cache-Control", "public, max-age=31536000")
            now := time.Now()
            cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
            c.Header("Expires", cacheExpire)
            c.Header("Cache-Control", "public, max-age=2592000")
            c.Header("ETag", getEtag(body))

            contentType := response.Header.Get("Content-Type")
            c.Data(http.StatusOK, contentType, body)
        }),
    )

    return nil
}

Do you mind to review it a bit ?

Lot of thanks for any minutes and insights you can provide on that. :-)

Cheers,
Luc Michalski

Hey @lucmichalski,

just remove the cache bpCache and replace the makeless event redis with the basic event component like in the makeless demo: https://github.com/makeless/makeless-demo/blob/master/backend/main.go#L78

Just to let you know that I used qor admin, and wrote a simple RBAC, as I could not make it ork, sadly, with makeless