blueprints

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: Apache-2.0 Imports: 0 Imported by: 0

README

GOT Blueprints

Public Community blueprints used by GOT CLI to generate Go/Fiber projects.

The package contains the standard, microservice, workspace-supporting, JWT, upload, Swagger/OpenAPI, database, logging, and layered API source templates used by GOT Community. Blueprints are compiled into the CLI so generation can run without downloading templates at runtime.

Requirements

  • Go 1.22 or newer

Validation

go fmt ./...
go vet ./...
go test ./...
go build ./...

Versioning

GOT CLI pins a tagged version of this module. Changes that alter generated output should be reviewed together with the corresponding CLI tests.

License

Licensed under the Apache License 2.0.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ConfigViper = `` /* 863-byte string literal not displayed */
View Source
var ConfigYml = `` /* 419-byte string literal not displayed */
View Source
var EntityFile = `
	package {{ .ApiName }}

	import "time"

	type {{ .TypeName }} struct {
		ID        int
		CreatedAt time.Time
	}
`
View Source
var GitIgnore = `` /* 735-byte string literal not displayed */
View Source
var HTTPResponse = `package response

import (
	"errors"
	"net/http"

	"github.com/gofiber/fiber/v2"
)

type Envelope struct {
	Status int ` + "`json:\"status\"`" + `
	Message string ` + "`json:\"message\"`" + `
	Data any ` + "`json:\"data\"`" + `
}

func Send(c *fiber.Ctx, status int, data any) error {
	return c.Status(status).JSON(Envelope{Status: status, Message: http.StatusText(status), Data: data})
}

func SendMessage(c *fiber.Ctx, status int, message string, data any) error {
	if message == "" { message = http.StatusText(status) }
	return c.Status(status).JSON(Envelope{Status: status, Message: message, Data: data})
}

func ErrorHandler(c *fiber.Ctx, err error) error {
	status := fiber.StatusInternalServerError
	message := http.StatusText(status)
	var fiberError *fiber.Error
	if errors.As(err, &fiberError) { status= fiberError.Code; message=fiberError.Message }
	return c.Status(status).JSON(Envelope{Status: status, Message: message, Data: nil})
}
`
View Source
var HandlerInterface = `` /* 560-byte string literal not displayed */
View Source
var HandlerMethod = `` /* 664-byte string literal not displayed */
View Source
var JWTManager = `package auth

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/spf13/viper"
)

const (
	AccessTokenType  = "access"
	RefreshTokenType = "refresh"
	minimumSecretSize = 32
)

type Config struct {
	Issuer        string
	Audience      string
	AccessSecret  string
	RefreshSecret string
	AccessTTL     time.Duration
	RefreshTTL    time.Duration
}

type Claims struct {
	Roles     []string ` + "`json:\"roles,omitempty\"`" + `
	TokenType string   ` + "`json:\"token_type\"`" + `
	jwt.RegisteredClaims
}

type TokenPair struct {
	AccessToken  string ` + "`json:\"access_token\"`" + `
	RefreshToken string ` + "`json:\"refresh_token\"`" + `
	TokenType    string ` + "`json:\"token_type\"`" + `
	ExpiresIn    int64  ` + "`json:\"expires_in\"`" + `
}

type Manager struct{ config Config }

func NewManager(config Config) (*Manager, error) {
	if len(config.AccessSecret) < minimumSecretSize || len(config.RefreshSecret) < minimumSecretSize {
		return nil, fmt.Errorf("JWT access and refresh secrets must each contain at least %d bytes", minimumSecretSize)
	}
	if config.AccessSecret == config.RefreshSecret {
		return nil, errors.New("JWT access and refresh secrets must be different")
	}
	if config.Issuer == "" || config.Audience == "" {
		return nil, errors.New("JWT issuer and audience are required")
	}
	if config.AccessTTL <= 0 || config.RefreshTTL <= 0 || config.AccessTTL >= config.RefreshTTL {
		return nil, errors.New("JWT TTL values must be positive and access TTL must be shorter than refresh TTL")
	}
	return &Manager{config: config}, nil
}

func NewManagerFromViper() (*Manager, error) {
	return NewManager(Config{
		Issuer: viper.GetString("JWT_ISSUER"), Audience: viper.GetString("JWT_AUDIENCE"),
		AccessSecret: viper.GetString("JWT_ACCESS_SECRET"), RefreshSecret: viper.GetString("JWT_REFRESH_SECRET"),
		AccessTTL: viper.GetDuration("JWT_ACCESS_TTL"), RefreshTTL: viper.GetDuration("JWT_REFRESH_TTL"),
	})
}

func (m *Manager) IssuePair(subject string, roles []string) (TokenPair, error) {
	if subject == "" { return TokenPair{}, errors.New("JWT subject is required") }
	now := time.Now().UTC()
	access, err := m.sign(subject, roles, AccessTokenType, now, m.config.AccessTTL, m.config.AccessSecret)
	if err != nil { return TokenPair{}, err }
	refresh, err := m.sign(subject, roles, RefreshTokenType, now, m.config.RefreshTTL, m.config.RefreshSecret)
	if err != nil { return TokenPair{}, err }
	return TokenPair{AccessToken: access, RefreshToken: refresh, TokenType: "Bearer", ExpiresIn: int64(m.config.AccessTTL.Seconds())}, nil
}

func (m *Manager) Refresh(refreshToken string) (TokenPair, error) {
	claims, err := m.ParseRefresh(refreshToken)
	if err != nil { return TokenPair{}, err }
	return m.IssuePair(claims.Subject, claims.Roles)
}

func (m *Manager) ParseAccess(token string) (*Claims, error) {
	return m.parse(token, AccessTokenType, m.config.AccessSecret)
}

func (m *Manager) ParseRefresh(token string) (*Claims, error) {
	return m.parse(token, RefreshTokenType, m.config.RefreshSecret)
}

func (m *Manager) sign(subject string, roles []string, tokenType string, now time.Time, ttl time.Duration, secret string) (string, error) {
	id, err := tokenID()
	if err != nil { return "", fmt.Errorf("create token ID: %w", err) }
	claims := Claims{Roles: roles, TokenType: tokenType, RegisteredClaims: jwt.RegisteredClaims{
		Issuer: m.config.Issuer, Subject: subject, Audience: jwt.ClaimStrings{m.config.Audience}, ID: id,
		IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
	}}
	return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
}

func (m *Manager) parse(raw, expectedType, secret string) (*Claims, error) {
	claims := &Claims{}
	token, err := jwt.ParseWithClaims(raw, claims, func(token *jwt.Token) (any, error) {
		if token.Method != jwt.SigningMethodHS256 { return nil, fmt.Errorf("unexpected JWT signing method %q", token.Method.Alg()) }
		return []byte(secret), nil
	}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithIssuer(m.config.Issuer),
		jwt.WithAudience(m.config.Audience), jwt.WithExpirationRequired(), jwt.WithIssuedAt())
	if err != nil { return nil, fmt.Errorf("invalid %s token: %w", expectedType, err) }
	if !token.Valid { return nil, fmt.Errorf("invalid %s token", expectedType) }
	if claims.TokenType != expectedType { return nil, fmt.Errorf("expected %s token", expectedType) }
	return claims, nil
}

func tokenID() (string, error) {
	value := make([]byte, 16)
	if _, err := rand.Read(value); err != nil { return "", err }
	return hex.EncodeToString(value), nil
}
`
View Source
var JWTMiddleware = `` /* 804-byte string literal not displayed */
View Source
var MainFile = `` /* 979-byte string literal not displayed */
View Source
var MicroserviceDockerIgnore = `.git
.github
bin
logs
uploads
*.log
.env
README.md
`
View Source
var MicroserviceDockerfile = `` /* 370-byte string literal not displayed */
View Source
var MicroserviceHealth = `` /* 768-byte string literal not displayed */
View Source
var MicroserviceMain = `` /* 1641-byte string literal not displayed */
View Source
var ORMInterface = `` /* 1216-byte string literal not displayed */
View Source
var RepositoryInterface = `` /* 554-byte string literal not displayed */
View Source
var RepositoryMethod = `` /* 549-byte string literal not displayed */
View Source
var RequestLoggerMiddleware = `` /* 1208-byte string literal not displayed */
View Source
var RouteDynamic = `` /* 727-byte string literal not displayed */
View Source
var RouteRoot = `` /* 446-byte string literal not displayed */
View Source
var RouteV1 = `` /* 333-byte string literal not displayed */
View Source
var SQLInterface = `` /* 415-byte string literal not displayed */
View Source
var SchemaFile = `` /* 362-byte string literal not displayed */
View Source
var ServiceInterface = `` /* 745-byte string literal not displayed */
View Source
var ServiceMethod = `` /* 655-byte string literal not displayed */
View Source
var StructuredLogger = `` /* 4769-byte string literal not displayed */
View Source
var SwaggerDocument = `` /* 147-byte string literal not displayed */
View Source
var SwaggerRoute = `package routes

import (
	"encoding/json"
	"strings"

	"github.com/gofiber/fiber/v2"
	"github.com/spf13/viper"
	"{{ .ModuleName }}/docs"
)

const swaggerUI = ` + "`" + `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>API documentation</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
  <script>
    window.onload = () => SwaggerUIBundle({
      url: "/swagger/openapi.json",
      dom_id: "#swagger-ui",
      deepLinking: true,
      persistAuthorization: true
    });
  </script>
</body>
</html>` + "`" + `

func RegisterSwaggerRoutes(app *fiber.App) {
	if !viper.GetBool("SWAGGER_ENABLED") || strings.EqualFold(viper.GetString("APP_ENV"), "production") {
		return
	}
	app.Get("/swagger", func(c *fiber.Ctx) error {
		return c.Redirect("/swagger/index.html", fiber.StatusTemporaryRedirect)
	})
	app.Get("/swagger/index.html", func(c *fiber.Ctx) error {
		return c.Type("html", "utf-8").SendString(swaggerUI)
	})
	app.Get("/swagger/openapi.json", func(c *fiber.Ctx) error {
		return c.Type("json", "utf-8").Send(buildOpenAPISpec(app))
	})
}

func buildOpenAPISpec(app *fiber.App) []byte {
	var document map[string]any
	if err := json.Unmarshal(docs.OpenAPI, &document); err != nil {
		return docs.OpenAPI
	}
	paths, ok := document["paths"].(map[string]any)
	if !ok {
		paths = make(map[string]any)
		document["paths"] = paths
	}
	for _, route := range app.GetRoutes() {
		method := strings.ToLower(route.Method)
		if method == "head" || method == "options" || strings.HasPrefix(route.Path, "/swagger") {
			continue
		}
		path, parameters := openAPIPath(route.Path)
		operations, ok := paths[path].(map[string]any)
		if !ok {
			operations = make(map[string]any)
			paths[path] = operations
		}
		if _, exists := operations[method]; exists {
			continue
		}
		operation := map[string]any{
			"summary": strings.ToUpper(route.Method) + " " + route.Path,
			"tags": []string{"Generated routes"},
			"responses": map[string]any{
				"200": map[string]any{
					"description": "Successful response",
					"content": map[string]any{
						"application/json": map[string]any{
							"schema": map[string]any{"$ref": "#/components/schemas/Envelope"},
						},
					},
				},
			},
		}
		if len(parameters) > 0 {
			operation["parameters"] = parameters
		}
		operations[method] = operation
	}
	encoded, err := json.MarshalIndent(document, "", "  ")
	if err != nil {
		return docs.OpenAPI
	}
	return encoded
}

func openAPIPath(path string) (string, []map[string]any) {
	segments := strings.Split(path, "/")
	parameters := make([]map[string]any, 0)
	for index, segment := range segments {
		if !strings.HasPrefix(segment, ":") {
			continue
		}
		name := strings.TrimSuffix(strings.TrimPrefix(segment, ":"), "?")
		if name == "" {
			continue
		}
		segments[index] = "{" + name + "}"
		parameters = append(parameters, map[string]any{
			"name": name, "in": "path", "required": true,
			"schema": map[string]any{"type": "string"},
		})
	}
	return strings.Join(segments, "/"), parameters
}
`
View Source
var SwaggerSpec = `` /* 1440-byte string literal not displayed */
View Source
var UploadHandler = `` /* 785-byte string literal not displayed */
View Source
var UploadRoute = `` /* 599-byte string literal not displayed */
View Source
var UploadService = `` /* 1275-byte string literal not displayed */
View Source
var UploadStorage = `package upload

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type File struct {
	OriginalName string ` + "`json:\"original_name\"`" + `
	StoredName string ` + "`json:\"stored_name\"`" + `
	Size int64 ` + "`json:\"size\"`" + `
	ContentType string ` + "`json:\"content_type\"`" + `
}

type Storage interface { Save(ctx context.Context, file *multipart.FileHeader) (File,error) }

type LocalStorage struct{ directory string }

func NewLocalStorage(directory string) *LocalStorage {
	if strings.TrimSpace(directory)=="" { directory="uploads" }
	return &LocalStorage{directory:directory}
}

func (s *LocalStorage) Save(ctx context.Context, header *multipart.FileHeader) (File,error) {
	if err:=ctx.Err();err!=nil{return File{},err}
	if err:=os.MkdirAll(s.directory,0o750);err!=nil{return File{},fmt.Errorf("create upload directory: %w",err)}
	source,err:=header.Open();if err!=nil{return File{},fmt.Errorf("open uploaded file: %w",err)}
	defer source.Close()
	random:=make([]byte,16);if _,err:=rand.Read(random);err!=nil{return File{},fmt.Errorf("create upload name: %w",err)}
	extension:=strings.ToLower(filepath.Ext(filepath.Base(header.Filename)))
	storedName:=hex.EncodeToString(random)+extension
	targetPath:=filepath.Join(s.directory,storedName)
	target,err:=os.OpenFile(targetPath,os.O_WRONLY|os.O_CREATE|os.O_EXCL,0o640)
	if err!=nil{return File{},fmt.Errorf("create upload target: %w",err)}
	prefix:=make([]byte,512);count,readErr:=source.Read(prefix);if readErr!=nil && readErr!=io.EOF { _=target.Close();_=os.Remove(targetPath);return File{},fmt.Errorf("inspect uploaded file: %w",readErr) }
	written,copyErr:=io.Copy(target,io.MultiReader(bytes.NewReader(prefix[:count]),source));closeErr:=target.Close()
	if copyErr!=nil { _=os.Remove(targetPath); return File{},fmt.Errorf("store uploaded file: %w",copyErr) }
	if closeErr!=nil { _=os.Remove(targetPath); return File{},fmt.Errorf("close uploaded file: %w",closeErr) }
	return File{OriginalName:filepath.Base(header.Filename),StoredName:storedName,Size:written,ContentType:http.DetectContentType(prefix[:count])},nil
}
`

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL