1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-15 00:15:15 +02:00
Files
golang-saas-starter-kit/cmd/web-app/handlers/routes.go

287 lines
13 KiB
Go
Raw Normal View History

package handlers
import (
"context"
"fmt"
"log"
"net/http"
"os"
2019-08-06 17:04:37 -08:00
"path/filepath"
"time"
"geeks-accelerator/oss/saas-starter-kit/internal/account"
"geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference"
"geeks-accelerator/oss/saas-starter-kit/internal/checklist"
"geeks-accelerator/oss/saas-starter-kit/internal/geonames"
"geeks-accelerator/oss/saas-starter-kit/internal/mid"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
"geeks-accelerator/oss/saas-starter-kit/internal/signup"
"geeks-accelerator/oss/saas-starter-kit/internal/user"
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
"geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite"
"geeks-accelerator/oss/saas-starter-kit/internal/user_auth"
"geeks-accelerator/oss/saas-starter-kit/internal/webroute"
"github.com/aws/aws-sdk-go/aws/session"
2019-08-06 17:04:37 -08:00
"github.com/ikeikeikeike/go-sitemap-generator/v2/stm"
2019-05-23 14:32:24 -05:00
"github.com/jmoiron/sqlx"
2019-07-13 16:32:29 -08:00
"gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis"
)
const (
TmplLayoutBase = "base.gohtml"
tmplLayoutSite = "site.gohtml"
TmplContentErrorGeneric = "error-generic.gohtml"
)
2019-08-14 17:59:47 -08:00
type AppContext struct {
Log *log.Logger
Env webcontext.Env
MasterDB *sqlx.DB
MasterDbHost string
2019-08-14 17:59:47 -08:00
Redis *redis.Client
UserRepo *user.Repository
UserAccountRepo *user_account.Repository
AccountRepo *account.Repository
AccountPrefRepo *account_preference.Repository
AuthRepo *user_auth.Repository
SignupRepo *signup.Repository
InviteRepo *invite.Repository
ChecklistRepo *checklist.Repository
GeoRepo *geonames.Repository
2019-08-14 17:59:47 -08:00
Authenticator *auth.Authenticator
StaticDir string
TemplateDir string
Renderer web.Renderer
WebRoute webroute.WebRoute
2019-08-14 17:59:47 -08:00
PreAppMiddleware []web.Middleware
PostAppMiddleware []web.Middleware
AwsSession *session.Session
2019-08-14 17:59:47 -08:00
}
// API returns a handler for a set of routes.
2019-08-14 17:59:47 -08:00
func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler {
2019-07-12 11:41:41 -08:00
2019-08-14 17:59:47 -08:00
// Include the pre middlewares first.
middlewares := appCtx.PreAppMiddleware
2019-07-12 11:41:41 -08:00
2019-08-14 17:59:47 -08:00
// Define app middlewares applied to all requests.
middlewares = append(middlewares,
mid.Trace(),
mid.Logger(appCtx.Log),
mid.Errors(appCtx.Log, appCtx.Renderer),
mid.Metrics(),
mid.Panics())
// Append any global middlewares that should be included after the app middlewares.
if len(appCtx.PostAppMiddleware) > 0 {
middlewares = append(middlewares, appCtx.PostAppMiddleware...)
2019-07-12 11:41:41 -08:00
}
// Construct the web.App which holds all routes as well as common Middleware.
2019-08-14 17:59:47 -08:00
app := web.NewApp(shutdown, appCtx.Log, appCtx.Env, middlewares...)
// Register serverless endpoint. This route is not authenticated.
serverless := Serverless{
MasterDB: appCtx.MasterDB,
MasterDbHost: appCtx.MasterDbHost,
AwsSession: appCtx.AwsSession,
Renderer: appCtx.Renderer,
}
app.Handle("GET", "/serverless/pending", serverless.Pending)
// waitDbMid ensures the database is active before allowing the user to access the requested URI.
waitDbMid := mid.WaitForDbResumed(mid.WaitForDbResumedConfig{
// Database handle to be used to ensure its online.
DB: appCtx.MasterDB,
// WaitHandler defines the handler to render for the user to when the database is being resumed.
WaitHandler: serverless.Pending,
})
2019-08-06 17:04:37 -08:00
// Build a sitemap.
sm := stm.NewSitemap(1)
sm.SetVerbose(false)
sm.SetDefaultHost(appCtx.WebRoute.WebAppUrl(""))
2019-08-06 17:04:37 -08:00
sm.Create()
smLocAddModified := func(loc stm.URL, filename string) {
2019-08-14 17:59:47 -08:00
contentPath := filepath.Join(appCtx.TemplateDir, "content", filename)
2019-08-06 17:04:37 -08:00
file, err := os.Stat(contentPath)
if err != nil {
log.Fatalf("main : Add sitemap file modified for %s: %+v", filename, err)
}
lm := []interface{}{"lastmod", file.ModTime().Format(time.RFC3339)}
loc = append(loc, lm)
sm.Add(loc)
}
// Register checklist management pages.
p := Checklists{
ChecklistRepo: appCtx.ChecklistRepo,
Redis: appCtx.Redis,
Renderer: appCtx.Renderer,
}
app.Handle("POST", "/checklists/:checklist_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/checklists/:checklist_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("POST", "/checklists/:checklist_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/checklists/:checklist_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("POST", "/checklists/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/checklists/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/checklists", p.Index, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
2019-08-04 23:24:30 -08:00
// Register user management pages.
us := Users{
2019-08-14 17:59:47 -08:00
UserRepo: appCtx.UserRepo,
UserAccountRepo: appCtx.UserAccountRepo,
AuthRepo: appCtx.AuthRepo,
InviteRepo: appCtx.InviteRepo,
GeoRepo: appCtx.GeoRepo,
2019-08-14 17:59:47 -08:00
Redis: appCtx.Redis,
Renderer: appCtx.Renderer,
2019-08-04 23:24:30 -08:00
}
2019-08-14 17:59:47 -08:00
app.Handle("POST", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("POST", "/users/:user_id", us.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/users/:user_id", us.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
2019-08-05 13:27:23 -08:00
app.Handle("POST", "/users/invite/:hash", us.InviteAccept)
app.Handle("GET", "/users/invite/:hash", us.InviteAccept)
2019-08-14 17:59:47 -08:00
app.Handle("POST", "/users/invite", us.Invite, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/users/invite", us.Invite, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("POST", "/users/create", us.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/users/create", us.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/users", us.Index, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
2019-08-04 23:27:02 -08:00
// Register user management and authentication endpoints.
u := UserRepos{
2019-08-14 17:59:47 -08:00
UserRepo: appCtx.UserRepo,
UserAccountRepo: appCtx.UserAccountRepo,
AccountRepo: appCtx.AccountRepo,
AuthRepo: appCtx.AuthRepo,
GeoRepo: appCtx.GeoRepo,
2019-08-14 17:59:47 -08:00
Renderer: appCtx.Renderer,
}
app.Handle("POST", "/user/login", u.Login)
app.Handle("GET", "/user/login", u.Login, waitDbMid)
app.Handle("GET", "/user/logout", u.Logout)
app.Handle("POST", "/user/reset-password/:hash", u.ResetConfirm)
app.Handle("GET", "/user/reset-password/:hash", u.ResetConfirm)
app.Handle("POST", "/user/reset-password", u.ResetPassword)
app.Handle("GET", "/user/reset-password", u.ResetPassword)
2019-08-14 17:59:47 -08:00
app.Handle("POST", "/user/update", u.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user/update", u.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user/account", u.Account, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user/virtual-login/:user_id", u.VirtualLogin, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("POST", "/user/virtual-login", u.VirtualLogin, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/user/virtual-login", u.VirtualLogin, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/user/virtual-logout", u.VirtualLogout, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user/switch-account/:account_id", u.SwitchAccount, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("POST", "/user/switch-account", u.SwitchAccount, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user/switch-account", u.SwitchAccount, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("POST", "/user", u.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
app.Handle("GET", "/user", u.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth())
// Register account management endpoints.
acc := Account{
2019-08-14 17:59:47 -08:00
AccountRepo: appCtx.AccountRepo,
AccountPrefRepo: appCtx.AccountPrefRepo,
AuthRepo: appCtx.AuthRepo,
Authenticator: appCtx.Authenticator,
GeoRepo: appCtx.GeoRepo,
2019-08-14 17:59:47 -08:00
Renderer: appCtx.Renderer,
}
2019-08-14 17:59:47 -08:00
app.Handle("POST", "/account/update", acc.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/account/update", acc.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("POST", "/account", acc.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("GET", "/account", acc.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin))
2019-08-14 17:59:47 -08:00
// Register signup endpoints.
s := Signup{
2019-08-14 17:59:47 -08:00
SignupRepo: appCtx.SignupRepo,
AuthRepo: appCtx.AuthRepo,
GeoRepo: appCtx.GeoRepo,
2019-08-14 17:59:47 -08:00
Renderer: appCtx.Renderer,
}
// This route is not authenticated
app.Handle("POST", "/signup", s.Step1)
app.Handle("GET", "/signup", s.Step1, waitDbMid)
2019-08-03 15:01:17 -08:00
// Register example endpoints.
ex := Examples{
2019-08-14 17:59:47 -08:00
Renderer: appCtx.Renderer,
2019-08-03 15:01:17 -08:00
}
2019-08-14 17:59:47 -08:00
app.Handle("POST", "/examples/flash-messages", ex.FlashMessages, mid.AuthenticateSessionOptional(appCtx.Authenticator))
app.Handle("GET", "/examples/flash-messages", ex.FlashMessages, mid.AuthenticateSessionOptional(appCtx.Authenticator))
app.Handle("GET", "/examples/images", ex.Images, mid.AuthenticateSessionOptional(appCtx.Authenticator))
2019-08-03 15:01:17 -08:00
2019-08-01 11:34:03 -08:00
// Register geo
g := Geo{
GeoRepo: appCtx.GeoRepo,
Redis: appCtx.Redis,
2019-08-01 11:34:03 -08:00
}
app.Handle("GET", "/geo/regions/autocomplete", g.RegionsAutocomplete)
app.Handle("GET", "/geo/postal_codes/autocomplete", g.PostalCodesAutocomplete)
app.Handle("GET", "/geo/geonames/postal_code/:postalCode", g.GeonameByPostalCode)
app.Handle("GET", "/geo/country/:countryCode/timezones", g.CountryTimezones)
2019-08-01 11:34:03 -08:00
// Register root
r := Root{
Renderer: appCtx.Renderer,
WebRoute: appCtx.WebRoute,
Sitemap: sm,
}
app.Handle("GET", "/api", r.SitePage)
app.Handle("GET", "/pricing", r.SitePage)
app.Handle("GET", "/support", r.SitePage)
app.Handle("GET", "/legal/privacy", r.SitePage)
app.Handle("GET", "/legal/terms", r.SitePage)
2019-08-14 17:59:47 -08:00
app.Handle("GET", "/", r.Index, mid.AuthenticateSessionOptional(appCtx.Authenticator))
app.Handle("GET", "/index.html", r.IndexHtml)
app.Handle("GET", "/robots.txt", r.RobotTxt)
2019-08-06 17:04:37 -08:00
app.Handle("GET", "/sitemap.xml", r.SitemapXml)
// Register health check endpoint. This route is not authenticated.
check := Check{
2019-08-14 17:59:47 -08:00
MasterDB: appCtx.MasterDB,
Redis: appCtx.Redis,
}
app.Handle("GET", "/v1/health", check.Health)
2019-08-28 23:57:33 -08:00
app.Handle("GET", "/ping", check.Ping)
// Add sitemap entries for Root.
smLocAddModified(stm.URL{{"loc", "/"}, {"changefreq", "weekly"}, {"mobile", true}, {"priority", 0.9}}, "site-index.gohtml")
smLocAddModified(stm.URL{{"loc", "/pricing"}, {"changefreq", "monthly"}, {"mobile", true}, {"priority", 0.8}}, "site-pricing.gohtml")
smLocAddModified(stm.URL{{"loc", "/support"}, {"changefreq", "monthly"}, {"mobile", true}, {"priority", 0.8}}, "site-support.gohtml")
smLocAddModified(stm.URL{{"loc", "/api"}, {"changefreq", "monthly"}, {"mobile", true}, {"priority", 0.7}}, "site-api.gohtml")
smLocAddModified(stm.URL{{"loc", "/legal/privacy"}, {"changefreq", "monthly"}, {"mobile", true}, {"priority", 0.5}}, "legal-privacy.gohtml")
smLocAddModified(stm.URL{{"loc", "/legal/terms"}, {"changefreq", "monthly"}, {"mobile", true}, {"priority", 0.5}}, "legal-terms.gohtml")
// Handle static files/pages. Render a custom 404 page when file not found.
static := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
2019-08-14 17:59:47 -08:00
err := web.StaticHandler(ctx, w, r, params, appCtx.StaticDir, "")
if err != nil {
if os.IsNotExist(err) {
rmsg := fmt.Sprintf("%s %s not found", r.Method, r.RequestURI)
err = weberror.NewErrorMessage(ctx, err, http.StatusNotFound, rmsg)
} else {
err = weberror.NewError(ctx, err, http.StatusInternalServerError)
}
2019-08-14 17:59:47 -08:00
return web.RenderError(ctx, w, r, err, appCtx.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
}
return nil
}
// Static file server
app.Handle("GET", "/*", static)
return app
}