Skip to content

Commit

Permalink
feat: restructure code and change build script
Browse files Browse the repository at this point in the history
  • Loading branch information
kodehat committed Feb 23, 2024
1 parent 9bf6819 commit 55bb152
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 191 deletions.
4 changes: 2 additions & 2 deletions .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ tmp_dir = "tmp"

[build]
args_bin = []
bin = "./tmp/main"
cmd = "templ generate && go build -o ./tmp/main ."
bin = "./portkey"
cmd = "templ generate . && ./build.sh"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
exclude_file = []
Expand Down
15 changes: 10 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM node:20.11.1-alpine3.18 AS frontend

ARG VERSION=dev

WORKDIR /usr/src/app

COPY package.json ./
Expand Down Expand Up @@ -27,7 +29,7 @@ COPY internal internal/

RUN apk add --no-cache git bash
RUN go install github.com/a-h/templ/cmd/templ@latest && templ generate
RUN bash build.sh
RUN sh build.sh $VERSION

FROM alpine:3.19.1

Expand All @@ -40,12 +42,15 @@ LABEL org.opencontainers.image.authors='[email protected]' \

WORKDIR /opt

COPY --from=backend /app/portkey /opt/app
COPY --from=backend /app/portkey ./app
# Provide a default config. Can be overwritten by mounting as volume by user.
COPY config.yml .

RUN adduser -D -H nonroot && \
chmod +x /opt/app
RUN apk add --no-cache tzdata && \
adduser -D -H nonroot && \
chmod +x ./app

EXPOSE 3000
EXPOSE 3000/tcp

USER nonroot:nonroot

Expand Down
42 changes: 19 additions & 23 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
#!/bin/sh

clear

TRG_PKG='main'
TARGET_PACKAGE='github.com/kodehat/portkey/internal/build'
BUILD_TIME=$(date -u +"%Y.%m.%d_%H:%M:%S")
CommitHash=N/A
GoVersion=N/A

if [[ $(go version) =~ [0-9]+\.[0-9]+\.[0-9]+ ]];
commit_hash=
version=${1:-dev}
go_version=unknown

latest_commit=$(git log -1 --pretty=format:%h || echo 'N/A')
if [[ latest_commit =~ 'fatal' ]];
then
GoVersion=${BASH_REMATCH[0]}
commit_hash=
else
commit_hash=$latest_commit
fi

GH=$(git log -1 --pretty=format:%h || echo 'N/A')
if [[ GH =~ 'fatal' ]];
if [[ $(go version) =~ [0-9]+\.[0-9]+\.[0-9]+ ]];
then
CommitHash=N/A
else
CommitHash=$GH
go_version=${BASH_REMATCH[0]}
fi

FLAG="-X $TRG_PKG.BuildTime=$BUILD_TIME"
FLAG="$FLAG -X $TRG_PKG.CommitHash=$CommitHash"
FLAG="$FLAG -X $TRG_PKG.GoVersion=$GoVersion"
FLAG="-X $TARGET_PACKAGE.BuildTime=$BUILD_TIME"
FLAG="$FLAG -X $TARGET_PACKAGE.CommitHash=$commit_hash"
FLAG="$FLAG -X $TARGET_PACKAGE.Version=$version"
FLAG="$FLAG -X $TARGET_PACKAGE.GoVersion=$go_version"

if [[ $1 =~ '-i' ]];
then
echo 'go install'
go install -v -ldflags "$FLAG"
else
echo 'go build'
go build -v -ldflags "$FLAG"
fi
echo "[Go v${go_version}] Building portkey at ${BUILD_TIME} with commit ${commit_hash:-unknown} in version ${version}."

CGO_ENABLED=0 go build -ldflags "${FLAG}"
46 changes: 46 additions & 0 deletions internal/build/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package build

// Variables that can be injected during build.
var (

// BuildTime time when the application was build.
BuildTime string = "unknown"

// CommitHash Git commit hash of the built application.
CommitHash string

// Version version of the built application.
Version string = "dev"

// GoVersion Go version the application was build with.
GoVersion string = "unknown"
)

// BuildDetails struct that contains information about the built application.
type BuildDetails struct {

// BuildTime time when the application was build.
BuildTime string

// CommitHash Git commit hash of the built application.
CommitHash string

// Version version of the built application.
Version string

// GoVersion Go version the application was build with.
GoVersion string
}

// B contains the build details of the application.
var B BuildDetails = newBuildDetails()

// newBuildDetails loads the build details struct B during startup.
func newBuildDetails() BuildDetails {
return BuildDetails{
BuildTime: BuildTime,
CommitHash: CommitHash,
Version: Version,
GoVersion: GoVersion,
}
}
5 changes: 3 additions & 2 deletions internal/components/utils.templ
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package components

import "github.com/kodehat/portkey/internal/types"
import "github.com/kodehat/portkey/internal/build"

templ Version(buildDetails types.BuildDetails) {
templ Version(buildDetails build.BuildDetails) {
<ul class="list-none">
<li>Build time: { buildDetails.BuildTime }</li>
if buildDetails.CommitHash != "" {
<li>Commit hash: <a class="no-underline border-b-2 border-solid border-slate-700 dark:border-slate-300 hover:text-slate-800 dark:hover:text-slate-300" target="_blank" rel="nofollow" href={ templ.URL("https://github.com/kodehat/portkey/commit/" + buildDetails.CommitHash) }>{ buildDetails.CommitHash }</a></li>
}
<li>Version: { buildDetails.Version }</li>
<li>Go version: { buildDetails.GoVersion }</li>
</ul>
}
Expand Down
70 changes: 70 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package config

import (
"flag"
"fmt"
"log"
"path/filepath"
"sort"
"strings"

"github.com/kodehat/portkey/internal/models"
"github.com/spf13/viper"
)

type Config struct {
Host string
Port int
Title string
FooterText string
SortAlphabetically bool
Portals []models.Portal
Pages []models.Page
}

type Flags struct {
ConfigPath string
}

var C Config
var F Flags

func init() {
loadFlags()
configPath, err := filepath.Abs(F.ConfigPath)
if err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
log.Printf("Looking for config.y[a]ml in: %s\n", configPath)
loadConfig(F.ConfigPath)
}

func loadFlags() {
var configPath string
flag.StringVar(&configPath, "config-path", ".", "path where config.yml can be found")
flag.Parse()
F = Flags{
ConfigPath: configPath,
}
}

func loadConfig(configPath string) {
viper.SetConfigName("config")
viper.SetConfigType("yml")
viper.AddConfigPath(configPath)
viper.SetDefault("host", "localhost")
viper.SetDefault("port", "1414")
viper.SetDefault("title", "Your Portal")
viper.SetDefault("footerText", "Works like a portal.")
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
viper.Unmarshal(&C)
if C.SortAlphabetically {
sort.Slice(C.Portals, func(i, j int) bool {
return strings.ToLower(C.Portals[i].Title) < strings.ToLower(C.Portals[j].Title)
})
}
}
36 changes: 36 additions & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package models

// Portal struct containing information about a portal.
// This is used later as a link destination shown to the user.
type Portal struct {

// Link destination link of a portal.
Link string `json:"link"`

// Title of a destination link.
Title string `json:"title"`

// Emoji used as a prefix of the title.
Emoji string `json:"emoji"`

// External defines if a destination link opens an external page or a custom page.
External bool `json:"external"`

// Keywords allows defining additional keywords used by the search.
// This can make getting reasonable search results a lot easier.
Keywords []string `json:"keywords"`
}

// Page struct defines a custom page that consists of a heading, content and a path,
// where the page will be available at.
type Page struct {

// Heading of the custom page.
Heading string `json:"heading"`

// Content of the custom page interpreted as HTML.
Content string `json:"content"`

// Path of the custom page.
Path string `json:"path"`
}
30 changes: 30 additions & 0 deletions internal/routes/home_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package routes

import (
"net/http"
"sync"

"github.com/a-h/templ"
"github.com/kodehat/portkey/internal/components"
"github.com/kodehat/portkey/internal/config"
"github.com/kodehat/portkey/internal/utils"
)

func homeHandler() http.HandlerFunc {
var (
init sync.Once
allFooterPortals []templ.Component
)
init.Do(func() {
allFooterPortals = getAllFooterPortals()
})
home := components.HomePage()
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
w.WriteHeader(http.StatusNotFound)
templ.Handler(components.ContentLayout(utils.PageTitle("404 Not Found", config.C.Title), "404 Not Found", components.NotFound(), allFooterPortals, config.C.FooterText)).ServeHTTP(w, r)
return
}
templ.Handler(components.HomeLayout(config.C.Title, home)).ServeHTTP(w, r)
}
}
35 changes: 35 additions & 0 deletions internal/routes/page_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package routes

import (
"net/http"
"sync"

"github.com/a-h/templ"
"github.com/kodehat/portkey/internal/components"
"github.com/kodehat/portkey/internal/config"
"github.com/kodehat/portkey/internal/utils"
)

type pageHandlerInfo struct {
pagePath string
handlerFunc func(w http.ResponseWriter, r *http.Request)
}

func pageHandler() []pageHandlerInfo {
var (
init sync.Once
allFooterPortals []templ.Component
)
init.Do(func() {
allFooterPortals = getAllFooterPortals()
})

var pageHandlerInfos = make([]pageHandlerInfo, len(config.C.Pages))
for i, page := range config.C.Pages {
pageHandlerInfos[i] = pageHandlerInfo{
pagePath: page.Path,
handlerFunc: templ.Handler(components.ContentLayout(utils.PageTitle(page.Heading, config.C.Title), page.Heading, components.ContentPage(page.Content), allFooterPortals, config.C.FooterText)).ServeHTTP,
}
}
return pageHandlerInfos
}
29 changes: 29 additions & 0 deletions internal/routes/portals_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package routes

import (
"net/http"
"strings"

"github.com/a-h/templ"
"github.com/kodehat/portkey/internal/components"
"github.com/kodehat/portkey/internal/config"
"github.com/kodehat/portkey/internal/utils"
)

func portalsHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("search")
var allHomePortals = make([]templ.Component, 0)
for _, configPortal := range config.C.Portals {
portal := components.HomePortal(configPortal.Link, configPortal.Emoji, configPortal.Title, configPortal.External)
if query != "" {
if strings.Contains(configPortal.Title, query) || utils.ArrSubStr(configPortal.Keywords, query) {
allHomePortals = append(allHomePortals, portal)
}
} else {
allHomePortals = append(allHomePortals, portal)
}
}
components.PortalPartial(allHomePortals).Render(r.Context(), w)
}
}
24 changes: 24 additions & 0 deletions internal/routes/rest_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package routes

import (
"encoding/json"
"net/http"

"github.com/kodehat/portkey/internal/config"
)

func portalsRestHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(config.C.Portals)
}
}

func pagesRestHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(config.C.Pages)
}
}
Loading

0 comments on commit 55bb152

Please sign in to comment.