Skip to content

Commit

Permalink
Refactor frontend build and name space all admin URIs behind /admin/.
Browse files Browse the repository at this point in the history
- Namespace all admin UI URLs behind `/admin/*`.
  This breaks the current admin UI URLs.
- Make Vue output build assets to `frontend/dist/*` instead of
  `frontend/dist/frontend`.
- Namespace Vue static assets to `/admin/static/*`.

This commit reduces the cofusing and convoluted Vue+WebPack build URI
and static path schemes. In addition, it removes ambiguity in URLs
where non-UI URLs like `/public`, `/api`, `/webhooks` etc. were in the
same name space as UI URLs like `/campaigns`, `/lists` etc. Now all UI
URLs are behind `/admin/`, also simplifying security rules for proxies.
  • Loading branch information
knadh committed Sep 23, 2021
1 parent 13f1648 commit bb340b8
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 37 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ STATIC := config.toml.sample \
schema.sql queries.sql \
static/public:/public \
static/email-templates \
frontend/dist/frontend:/frontend \
frontend/dist:/admin \
i18n:/i18n

.PHONY: build
Expand All @@ -40,14 +40,14 @@ $(FRONTEND_YARN_MODULES): frontend/package.json frontend/yarn.lock
$(BIN): $(shell find . -type f -name "*.go")
CGO_ENABLED=0 go build -o ${BIN} -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}'" cmd/*.go

# Run the backend in dev mode. The frontend assets in dev mode are loaded from disk from frontend/dist/frontend.
# Run the backend in dev mode. The frontend assets in dev mode are loaded from disk from frontend/dist.
.PHONY: run
run:
CGO_ENABLED=0 go run -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}' -X 'main.frontendDir=frontend/dist/frontend'" cmd/*.go
CGO_ENABLED=0 go run -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}' -X 'main.frontendDir=frontend/dist'" cmd/*.go

# Build the JS frontend into frontend/dist.
$(FRONTEND_DIST): $(FRONTEND_DEPS)
export VUE_APP_VERSION="${VERSION}" && cd frontend && $(YARN) build && mv dist/favicon.png dist/frontend/favicon.png
export VUE_APP_VERSION="${VERSION}" && cd frontend && $(YARN) build
touch --no-create $(FRONTEND_DIST)


Expand Down
37 changes: 15 additions & 22 deletions cmd/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/subtle"
"net/http"
"net/url"
"path"
"regexp"
"strconv"

Expand Down Expand Up @@ -37,7 +38,7 @@ var (
)

// registerHandlers registers HTTP handlers.
func registerHTTPHandlers(e *echo.Echo, app *App) {
func initHTTPHandlers(e *echo.Echo, app *App) {
// Group of private handlers with BasicAuth.
var g *echo.Group

Expand All @@ -48,7 +49,15 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
g = e.Group("", middleware.BasicAuth(basicAuth))
}

g.GET("/", handleIndexPage)
// Admin JS app views.
// /admin/static/* file server is registered in initHTTPServer().
g.GET("/", func(c echo.Context) error {
return c.Redirect(http.StatusPermanentRedirect, path.Join(adminRoot, ""))
})
g.GET(path.Join(adminRoot, ""), handleAdminPage)
g.GET(path.Join(adminRoot, "/*"), handleAdminPage)

// API endpoints.
g.GET("/api/health", handleHealthCheck)
g.GET("/api/config", handleGetServerConfig)
g.GET("/api/lang/:lang", handleGetI18nLang)
Expand Down Expand Up @@ -125,21 +134,6 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
g.PUT("/api/templates/:id/default", handleTemplateSetDefault)
g.DELETE("/api/templates/:id", handleDeleteTemplate)

// Static admin views.
g.GET("/lists", handleIndexPage)
g.GET("/lists/forms", handleIndexPage)
g.GET("/subscribers", handleIndexPage)
g.GET("/subscribers/lists/:listID", handleIndexPage)
g.GET("/subscribers/import", handleIndexPage)
g.GET("/subscribers/bounces", handleIndexPage)
g.GET("/campaigns", handleIndexPage)
g.GET("/campaigns/new", handleIndexPage)
g.GET("/campaigns/media", handleIndexPage)
g.GET("/campaigns/templates", handleIndexPage)
g.GET("/campaigns/:campignID", handleIndexPage)
g.GET("/settings", handleIndexPage)
g.GET("/settings/logs", handleIndexPage)

if app.constants.BounceWebhooksEnabled {
// Private authenticated bounce endpoint.
g.POST("/webhooks/bounce", handleBounceWebhook)
Expand Down Expand Up @@ -171,17 +165,16 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
e.GET("/health", handleHealthCheck)
}

// handleIndex is the root handler that renders the Javascript frontend.
func handleIndexPage(c echo.Context) error {
// handleAdminPage is the root handler that renders the Javascript admin frontend.
func handleAdminPage(c echo.Context) error {
app := c.Get("app").(*App)

b, err := app.fs.Read("/frontend/index.html")
b, err := app.fs.Read(path.Join(adminRoot, "/index.html"))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}

c.Response().Header().Set("Content-Type", "text/html")
return c.String(http.StatusOK, string(b))
return c.HTMLBlob(http.StatusOK, b)
}

// handleHealthCheck is a healthcheck endpoint that returns a 200 response.
Expand Down
19 changes: 14 additions & 5 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import (

const (
queryFilePath = "queries.sql"

// Root URI of the admin frontend.
adminRoot = "/admin"
)

// constants contains static, constant config values required by the app.
Expand Down Expand Up @@ -129,9 +132,9 @@ func initFS(appDir, frontendDir, staticDir, i18nDir string) stuffbin.FileSystem
}

frontendFiles = []string{
// The app's frontend assets are accessible at /frontend/js/* during runtime.
// These paths are joined with frontendDir.
"./:/frontend",
// Admin frontend's static assets accessible at /admin/* during runtime.
// These paths are sourced from frontendDir.
"./:/admin",
}

staticFiles = []string{
Expand Down Expand Up @@ -574,15 +577,21 @@ func initHTTPServer(app *App) *echo.Echo {

// Initialize the static file server.
fSrv := app.fs.FileServer()

// Public (subscriber) facing static files.
srv.GET("/public/*", echo.WrapHandler(fSrv))
srv.GET("/frontend/*", echo.WrapHandler(fSrv))

// Admin (frontend) facing static files.
srv.GET("/admin/static/*", echo.WrapHandler(fSrv))

// Public (subscriber) facing media upload files.
if ko.String("upload.provider") == "filesystem" {
srv.Static(ko.String("upload.filesystem.upload_uri"),
ko.String("upload.filesystem.upload_path"))
}

// Register all HTTP handlers.
registerHTTPHandlers(srv, app)
initHTTPHandlers(srv, app)

// Start the server.
go func() {
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>frontend/favicon.png" />
<link rel="icon" href="<%= BASE_URL %>static/favicon.png" />
<link href="https://fonts.googleapis.com/css?family=Inter:400,600" rel="stylesheet" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
Expand Down
File renamed without changes
2 changes: 1 addition & 1 deletion frontend/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import store from '../store';
import { models } from '../constants';

const http = axios.create({
baseURL: process.env.BASE_URL,
baseURL: process.env.VUE_APP_API_URL || '/',
withCredentials: false,
responseType: 'json',

Expand Down
8 changes: 4 additions & 4 deletions frontend/vue.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
module.exports = {
publicPath: '/',
publicPath: '/admin',
outputDir: 'dist',

// This is to make all static file requests generated by Vue to go to
// /frontend/*. However, this also ends up creating a `dist/frontend`
// directory and moves all the static files in it. The physical directory
// and the URI for assets are tightly coupled. This is handled in the Go app
// by using stuffbin aliases.
assetsDir: 'frontend',
assetsDir: 'static',

// Move the index.html file from dist/index.html to dist/frontend/index.html
indexPath: './frontend/index.html',
// indexPath: './frontend/index.html',

productionSourceMap: false,
filenameHashing: true,
Expand All @@ -26,7 +26,7 @@ module.exports = {
devServer: {
port: process.env.LISTMONK_FRONTEND_PORT || 8080,
proxy: {
'^/(api|webhooks|subscription|public)': {
'^/(api|webhooks|subscription|public)|$': {
target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
}
}
Expand Down

0 comments on commit bb340b8

Please sign in to comment.