Compare commits

...

3 Commits

Author SHA1 Message Date
Florian Beisel 71ea9cac18
🎨 add helper functions and general cleanup 2024-01-24 11:04:43 +01:00
Florian Beisel 57722116d6
Add rules routes 2024-01-24 11:03:08 +01:00
Florian Beisel 0247413a10
Add a Dockerfile 2024-01-24 10:58:30 +01:00
12 changed files with 279 additions and 111 deletions

47
Dockerfile Normal file
View File

@ -0,0 +1,47 @@
# Copyright 2024 Florian Beisel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Start from a base image with Go installed and C libraries needed for SQLite.
FROM golang:1.21-bookworm as builder
# Set the working directory.
WORKDIR /app
# Copy the go.mod and go.sum files first and download the modules.
# This is to take advantage of Docker's cache layers.
COPY go.mod go.sum ./
RUN go mod download
# Copy the rest of the source code.
COPY . .
# Build the binary. CGO is enabled by default in the official Go images.
RUN go build -o app .
# Use Debian as the base for the runtime image as it includes necessary C libraries.
FROM debian:bookworm-slim
# Set the working directory in the container.
WORKDIR /root/
# Copy the binary from the builder stage.
COPY --from=builder /app/app .
# Copy the binary from the builder stage.
COPY --from=builder /app/db/migrations db/migrations
# Add any runtime dependencies here, if needed.
# Command to run the executable.
CMD ["./app"]

View File

@ -17,11 +17,19 @@ package api
import ( import (
"errors" "errors"
"net/http" "net/http"
"reflect"
"git.beisel.it/florian/hostname-service/rules" "git.beisel.it/florian/hostname-service/rules"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// @Summary Returns a list of available rules
// @Description Return a list of names of Rules which are known in the RulesRegistry
// @ID get-rules
// @Produce json
// @Security Bearer
// @Tags Querying Rules
// @Router /rules [get]
func ListAvailableRules(c *gin.Context) { func ListAvailableRules(c *gin.Context) {
descriptions := make(map[string]string) descriptions := make(map[string]string)
for category, descriptor := range rules.RulesRegistry { for category, descriptor := range rules.RulesRegistry {
@ -36,3 +44,44 @@ func getHostnameRuleByCategory(category string) (rules.HostnameRule, error) {
} }
return nil, errors.New("unknown category") return nil, errors.New("unknown category")
} }
// @Summary Returns details about a rule
// @Description Returns two obea an Input Object and an Output Object
// @Description describing the Rules stored values and required parameters
// @ID get-rule-details
// @Produce json
// @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Security Bearer
// @Tags Querying Rules
// @Router /rules/:rule [get]
func GetRuleStruct(c *gin.Context) {
ruleName := c.Param("rule")
descriptor, exists := rules.RulesRegistry[ruleName]
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": "Rule not found"})
return
}
// Create instances of the rule and its input struct
ruleInstance := descriptor.Factory()
inputInstance := reflect.New(reflect.TypeOf(ruleInstance).Elem()).Interface()
// Serialize instances to JSON
ruleJSON, err := rules.StructToJSON(ruleInstance)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error serializing rule struct"})
return
}
inputJSON, err := rules.StructToJSON(inputInstance)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error serializing input struct"})
return
}
c.JSON(http.StatusOK, gin.H{
"input": inputJSON,
"output": ruleJSON,
})
}

View File

@ -1,4 +1,4 @@
CREATE TABLE hostnames ( CREATE TABLE IF NOT EXISTS hostnames (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL, category TEXT NOT NULL,
hostname TEXT NOT NULL, hostname TEXT NOT NULL,

View File

@ -101,32 +101,6 @@ const docTemplate = `{
} }
} }
}, },
"/api/rules": {
"get": {
"description": "Get a list of all available hostname generation rules.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Rules"
],
"summary": "List Available Rules",
"responses": {
"200": {
"description": "List of available rules with descriptions",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/server": { "/api/server": {
"put": { "put": {
"security": [ "security": [
@ -286,6 +260,51 @@ const docTemplate = `{
} }
} }
}, },
"/rules": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Return a list of names of Rules which are known in the RulesRegistry",
"produces": [
"application/json"
],
"tags": [
"Querying Rules"
],
"summary": "Returns a list of available rules",
"operationId": "get-rules",
"responses": {}
}
},
"/rules/:rule": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Returns two obea an Input Object and an Output Object\ndescribing the Rules stored values and required parameters",
"produces": [
"application/json"
],
"tags": [
"Querying Rules"
],
"summary": "Returns details about a rule",
"operationId": "get-rule-details",
"responses": {
"200": {
"description": "Hostname",
"schema": {
"$ref": "#/definitions/models.SimpleHostnameResponse"
}
}
}
}
},
"/{category}": { "/{category}": {
"get": { "get": {
"security": [ "security": [

View File

@ -95,32 +95,6 @@
} }
} }
}, },
"/api/rules": {
"get": {
"description": "Get a list of all available hostname generation rules.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Rules"
],
"summary": "List Available Rules",
"responses": {
"200": {
"description": "List of available rules with descriptions",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/server": { "/api/server": {
"put": { "put": {
"security": [ "security": [
@ -280,6 +254,51 @@
} }
} }
}, },
"/rules": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Return a list of names of Rules which are known in the RulesRegistry",
"produces": [
"application/json"
],
"tags": [
"Querying Rules"
],
"summary": "Returns a list of available rules",
"operationId": "get-rules",
"responses": {}
}
},
"/rules/:rule": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Returns two obea an Input Object and an Output Object\ndescribing the Rules stored values and required parameters",
"produces": [
"application/json"
],
"tags": [
"Querying Rules"
],
"summary": "Returns details about a rule",
"operationId": "get-rule-details",
"responses": {
"200": {
"description": "Hostname",
"schema": {
"$ref": "#/definitions/models.SimpleHostnameResponse"
}
}
}
}
},
"/{category}": { "/{category}": {
"get": { "get": {
"security": [ "security": [

View File

@ -212,23 +212,6 @@ paths:
summary: Update hostname for category "notebook" summary: Update hostname for category "notebook"
tags: tags:
- Manipulate existing Hostnames - Manipulate existing Hostnames
/api/rules:
get:
consumes:
- application/json
description: Get a list of all available hostname generation rules.
produces:
- application/json
responses:
"200":
description: List of available rules with descriptions
schema:
additionalProperties:
type: string
type: object
summary: List Available Rules
tags:
- Rules
/api/server: /api/server:
post: post:
consumes: consumes:
@ -330,6 +313,36 @@ paths:
summary: User login summary: User login
tags: tags:
- Authentication - Authentication
/rules:
get:
description: Return a list of names of Rules which are known in the RulesRegistry
operationId: get-rules
produces:
- application/json
responses: {}
security:
- Bearer: []
summary: Returns a list of available rules
tags:
- Querying Rules
/rules/:rule:
get:
description: |-
Returns two obea an Input Object and an Output Object
describing the Rules stored values and required parameters
operationId: get-rule-details
produces:
- application/json
responses:
"200":
description: Hostname
schema:
$ref: '#/definitions/models.SimpleHostnameResponse'
security:
- Bearer: []
summary: Returns details about a rule
tags:
- Querying Rules
securityDefinitions: securityDefinitions:
Bearer: Bearer:
description: Type "Bearer" followed by a space and JWT token. description: Type "Bearer" followed by a space and JWT token.

1
go.mod
View File

@ -27,6 +27,7 @@ require (
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.5.0
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect

2
go.sum
View File

@ -22,6 +22,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=

View File

@ -19,6 +19,7 @@ import (
"git.beisel.it/florian/hostname-service/auth" "git.beisel.it/florian/hostname-service/auth"
"git.beisel.it/florian/hostname-service/docs" "git.beisel.it/florian/hostname-service/docs"
"git.beisel.it/florian/hostname-service/middleware" "git.beisel.it/florian/hostname-service/middleware"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
@ -27,7 +28,14 @@ import (
func New() *gin.Engine { func New() *gin.Engine {
gin.SetMode(gin.DebugMode) gin.SetMode(gin.DebugMode)
router := gin.Default() router := gin.Default()
// // Configure CORS
config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://localhost:3000", "http://localhost:8080", "*"} // Set to your frontend's URL
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
router.Use(cors.New(config))
docs.SwaggerInfo.Host = "localhost:8080" docs.SwaggerInfo.Host = "localhost:8080"
docs.SwaggerInfo.BasePath = "/api/v1" docs.SwaggerInfo.BasePath = "/api/v1"
@ -62,7 +70,8 @@ func New() *gin.Engine {
authenticated.GET("/:category", api.ListHostnamesByCategory) authenticated.GET("/:category", api.ListHostnamesByCategory)
// List available Rules // List available Rules
authenticated.GET("/api/rules", api.ListAvailableRules) authenticated.GET("/rules", api.ListAvailableRules)
authenticated.GET("/rules/:rule", api.GetRuleStruct)
} }
} }

View File

@ -1,6 +1,7 @@
package rules package rules
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
@ -59,3 +60,11 @@ func (br *BaseRule) baseUpdate(rule HostnameRule, category string, oldhostname s
return newHostname, nil return newHostname, nil
} }
func StructToJSON(v interface{}) (string, error) {
bytes, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(bytes), nil
}

View File

@ -66,32 +66,32 @@ func (nr *NotebookRule) Generate(params map[string]interface{}) (string, []byte,
return hostname, paramsJSON, nil return hostname, paramsJSON, nil
} }
// @Summary Generate hostname for category "notebook" // @Summary Generate hostname for category "notebook"
// @Description Generates a hostname for a notebook based on dynamic rules. // @Description Generates a hostname for a notebook based on dynamic rules.
// @ID insert-notebook-hostname // @ID insert-notebook-hostname
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags Generating Hostnames // @Tags Generating Hostnames
// @Param body body NotebookRuleInput true "Input data to generate hostname" // @Param body body NotebookRuleInput true "Input data to generate hostname"
// @Success 200 {object} models.SimpleHostnameResponse "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/notebook [post] // @Router /api/notebook [post]
// @Security Bearer // @Security Bearer
func (nr *NotebookRule) Insert(category string, params map[string]interface{}) (string, error) { func (nr *NotebookRule) Insert(category string, params map[string]interface{}) (string, error) {
// Generate the hostname // Generate the hostname
return nr.baseInsert(nr, category, params) return nr.baseInsert(nr, category, params)
} }
// @Summary Update hostname for category "notebook" // @Summary Update hostname for category "notebook"
// @Description Generates a new hostname for a notebook based on dynamic rules. // @Description Generates a new hostname for a notebook based on dynamic rules.
// @ID update-notebook-hostname // @ID update-notebook-hostname
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags Manipulate existing Hostnames // @Tags Manipulate existing Hostnames
// @Param body body NotebookRuleInput true "Input data to generate hostname" // @Param body body NotebookRuleInput true "Input data to generate hostname"
// @Success 200 {object} models.SimpleHostnameResponse "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/notebook [put] // @Router /api/notebook [put]
// @Security Bearer // @Security Bearer
func (nr *NotebookRule) Update(category string, oldhostname string, params map[string]interface{}) (string, error) { func (nr *NotebookRule) Update(category string, oldhostname string, params map[string]interface{}) (string, error) {
return nr.baseUpdate(nr, category, oldhostname, params) return nr.baseUpdate(nr, category, oldhostname, params)
} }

View File

@ -108,32 +108,32 @@ func (sr *ServerRule) Generate(params map[string]interface{}) (string, []byte, e
} }
// @Summary Generate hostname for category "notebook" // @Summary Generate hostname for category "notebook"
// @Description Generates a hostname for a notebook based on dynamic rules. // @Description Generates a hostname for a notebook based on dynamic rules.
// @ID insert-server-hostname // @ID insert-server-hostname
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags Generating Hostnames // @Tags Generating Hostnames
// @Param body body ServerRuleInput true "Input data to generate hostname" // @Param body body ServerRuleInput true "Input data to generate hostname"
// @Success 200 {object} models.SimpleHostnameResponse "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/server [post] // @Router /api/server [post]
// @Security Bearer // @Security Bearer
func (nr *ServerRule) Insert(category string, params map[string]interface{}) (string, error) { func (nr *ServerRule) Insert(category string, params map[string]interface{}) (string, error) {
// Generate the hostname // Generate the hostname
return nr.baseInsert(nr, category, params) return nr.baseInsert(nr, category, params)
} }
// @Summary Update hostname for category "notebook" // @Summary Update hostname for category "notebook"
// @Description Generates a new hostname for a notebook based on dynamic rules. // @Description Generates a new hostname for a notebook based on dynamic rules.
// @ID update-server-hostname // @ID update-server-hostname
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags Manipulate existing Hostnames // @Tags Manipulate existing Hostnames
// @Param body body ServerRuleInput true "Input data to generate hostname" // @Param body body ServerRuleInput true "Input data to generate hostname"
// @Success 200 {object} models.SimpleHostnameResponse "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/server [put] // @Router /api/server [put]
// @Security Bearer // @Security Bearer
func (nr *ServerRule) Update(category string, oldhostname string, params map[string]interface{}) (string, error) { func (nr *ServerRule) Update(category string, oldhostname string, params map[string]interface{}) (string, error) {
return nr.baseUpdate(nr, category, oldhostname, params) return nr.baseUpdate(nr, category, oldhostname, params)
} }