Compare commits

..

4 Commits

Author SHA1 Message Date
Florian Beisel c08de78984
📝 Update generated documentation with new Models
This commit contains the new documentation for the API endpoints that
will now use the newly generated models that describe their reponse
correctly
2024-01-18 00:15:14 +01:00
Florian Beisel f0118ab8ea
🐛 Use OldHostname when querying the Database
In BaseRule.baseUpdate our goal is to check if a hostname exists before
trying to rename that hostname into something else. This patch fixes an
error where the newly generated Hostname was used to check for the
existence of the old hostname.
2024-01-18 00:11:39 +01:00
Florian Beisel ce4d469466
🚧 Begin implementing the Models 2024-01-18 00:10:48 +01:00
Florian Beisel 607682884c
📝 Create missing models for API documentation 2024-01-18 00:09:26 +01:00
10 changed files with 279 additions and 123 deletions

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"git.beisel.it/florian/hostname-service/db" "git.beisel.it/florian/hostname-service/db"
"git.beisel.it/florian/hostname-service/models"
"git.beisel.it/florian/hostname-service/rules" "git.beisel.it/florian/hostname-service/rules"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -65,19 +66,20 @@ func CreateOrUpdateHostname(c *gin.Context, isUpdate bool) {
return return
} }
c.JSON(http.StatusOK, gin.H{"hostname": hostname}) response := models.SimpleHostnameResponse{Hostname: hostname}
c.JSON(http.StatusOK, response)
} }
// @Summary Delete a hostname from the database // @Summary Delete a hostname from the database
// @Description List all details for a given category // @Description List all details for a given category
// @ID delete-hostnames-by-category-and-name // @ID delete-hostnames-by-category-and-name
// @Produce json // @Produce json
// @Param category path string true "Category of the hostname" // @Param category path string true "Category of the hostname"
// @Param hostname path string true "Hostname to delete" // @Param hostname path string true "Hostname to delete"
// @Success 200 {json} json "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Security Bearer // @Security Bearer
// @Tags Manipulate existing Hostnames // @Tags Manipulate existing Hostnames
// @Router /{category}/{hostname} [delete] // @Router /{category}/{hostname} [delete]
func DeleteHostname(c *gin.Context) { func DeleteHostname(c *gin.Context) {
category := c.Param("category") category := c.Param("category")
hostname := c.Param("hostname") hostname := c.Param("hostname")
@ -87,18 +89,19 @@ func DeleteHostname(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
c.JSON(http.StatusOK, gin.H{"hostname": hostname}) response := models.SimpleHostnameResponse{Hostname: hostname}
c.JSON(http.StatusOK, response)
} }
// @Summary Return a list of hosts and their details filtered by category // @Summary Return a list of hosts and their details filtered by category
// @Description List all details for a given category // @Description List all details for a given category
// @ID list-hostnames-by-category // @ID list-hostnames-by-category
// @Produce json // @Produce json
// @Param category path string true "Category of the hostname" // @Param category path string true "Category of the hostname"
// @Success 200 {json} json "Hostname" // @Success 200 {array} models.Hostname "An array of responses"
// @Security Bearer // @Security Bearer
// @Tags Querying Hostnames // @Tags Querying Hostnames
// @Router /{category} [get] // @Router /{category} [get]
func ListHostnamesByCategory(c *gin.Context) { func ListHostnamesByCategory(c *gin.Context) {
category := c.Param("category") category := c.Param("category")
@ -114,16 +117,16 @@ func ListHostnamesByCategory(c *gin.Context) {
c.JSON(http.StatusOK, hostnames) c.JSON(http.StatusOK, hostnames)
} }
// @Summary Return a single hostname by Category and Name // @Summary Return a single hostname by Category and Name
// @Description Return details for a single hostname identified by its category // @Description Return details for a single hostname identified by its category
// @ID get-hostname-by-category-and-name // @ID get-hostname-by-category-and-name
// @Produce json // @Produce json
// @Param category path string true "Category of the hostname" // @Param category path string true "Category of the hostname"
// @Param hostname path string true "Category of the hostname" // @Param hostname path string true "Category of the hostname"
// @Security Bearer // @Success 200 {object} models.Hostname "A single response object"
// @Success 200 {json} json "Hostname" // @Security Bearer
// @Tags Querying Hostnames // @Tags Querying Hostnames
// @Router /{category}/{hostname} [get] // @Router /{category}/{hostname} [get]
func GetHostnameByCategoryAndName(c *gin.Context) { func GetHostnameByCategoryAndName(c *gin.Context) {
category := c.Param("category") category := c.Param("category")
hostname := c.Param("hostname") hostname := c.Param("hostname")

View File

@ -11,17 +11,18 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// LoginHandler godoc // LoginHandler godoc
//
// @Summary User login // @Summary User login
// @Description Authenticate user and return JWT token // @Description Authenticate user and return JWT token
// @Tags Authentication // @Tags Authentication
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param loginCredentials body models.LoginCredentials true "Login Credentials" // @Param loginCredentials body models.LoginCredentials true "Login Credentials"
// @Success 200 {object} map[string]string "Successfully authenticated, JWT token returned" // @Success 200 {object} models.TokenResponse "Successfully authenticated, JWT token returned"
// @Failure 400 {object} map[string]string "Invalid request body" // @Failure 400 {object} models.ErrorResponse "Invalid request body"
// @Failure 401 {object} map[string]string "Invalid login credentials" // @Failure 401 {object} models.ErrorResponse "Invalid login credentials"
// @Failure 500 {object} map[string]string "Internal server error" // @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /login [post] // @Router /login [post]
func LoginHandler(c *gin.Context) { func LoginHandler(c *gin.Context) {
var creds models.LoginCredentials var creds models.LoginCredentials

View File

@ -52,7 +52,7 @@ const docTemplate = `{
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -85,7 +85,7 @@ const docTemplate = `{
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -148,37 +148,25 @@ const docTemplate = `{
"200": { "200": {
"description": "Successfully authenticated, JWT token returned", "description": "Successfully authenticated, JWT token returned",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.TokenResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"400": { "400": {
"description": "Invalid request body", "description": "Invalid request body",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"401": { "401": {
"description": "Invalid login credentials", "description": "Invalid login credentials",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"500": { "500": {
"description": "Internal server error", "description": "Internal server error",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
} }
} }
@ -211,9 +199,12 @@ const docTemplate = `{
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Hostname", "description": "An array of responses",
"schema": { "schema": {
"type": "json" "type": "array",
"items": {
"$ref": "#/definitions/models.Hostname"
}
} }
} }
} }
@ -253,9 +244,9 @@ const docTemplate = `{
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Hostname", "description": "A single response object",
"schema": { "schema": {
"type": "json" "$ref": "#/definitions/models.Hostname"
} }
} }
} }
@ -295,7 +286,7 @@ const docTemplate = `{
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "json" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -303,14 +294,72 @@ const docTemplate = `{
} }
}, },
"definitions": { "definitions": {
"models.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"models.Hostname": {
"description": "Model of the Hostname as it is represented in the database",
"type": "object",
"properties": {
"Category": {
"description": "Category / Rule that was used when generating the hostname",
"type": "string",
"example": "notebook"
},
"Created_at": {
"description": "Creation Time of the entry",
"type": "string",
"example": "2024-01-16T12:53:59Z"
},
"Hostname": {
"description": "Generated hostname",
"type": "string",
"example": "ISEHENNB0009"
},
"Id": {
"description": "Internal ID of the Hostname within the database",
"type": "integer",
"example": 25
},
"Parameters": {
"description": "Parameter object of rule specific attributes, see rule.* models",
"type": "object",
"additionalProperties": true
}
}
},
"models.LoginCredentials": { "models.LoginCredentials": {
"description": "User account information used in the login process with Username and password", "description": "User account information used in the login process with Username and password",
"type": "object", "type": "object",
"properties": { "properties": {
"password": { "Password": {
"type": "string" "type": "string"
}, },
"username": { "Username": {
"type": "string"
}
}
},
"models.SimpleHostnameResponse": {
"description": "Model of the Hostname as returned by POST endpoint",
"type": "object",
"properties": {
"Hostname": {
"description": "Name of the newly generated host",
"type": "string"
}
}
},
"models.TokenResponse": {
"description": "Model returned after successful login",
"type": "object",
"properties": {
"token": {
"type": "string" "type": "string"
} }
} }

View File

@ -46,7 +46,7 @@
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -79,7 +79,7 @@
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -142,37 +142,25 @@
"200": { "200": {
"description": "Successfully authenticated, JWT token returned", "description": "Successfully authenticated, JWT token returned",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.TokenResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"400": { "400": {
"description": "Invalid request body", "description": "Invalid request body",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"401": { "401": {
"description": "Invalid login credentials", "description": "Invalid login credentials",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
}, },
"500": { "500": {
"description": "Internal server error", "description": "Internal server error",
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/models.ErrorResponse"
"additionalProperties": {
"type": "string"
}
} }
} }
} }
@ -205,9 +193,12 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Hostname", "description": "An array of responses",
"schema": { "schema": {
"type": "json" "type": "array",
"items": {
"$ref": "#/definitions/models.Hostname"
}
} }
} }
} }
@ -247,9 +238,9 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Hostname", "description": "A single response object",
"schema": { "schema": {
"type": "json" "$ref": "#/definitions/models.Hostname"
} }
} }
} }
@ -289,7 +280,7 @@
"200": { "200": {
"description": "Hostname", "description": "Hostname",
"schema": { "schema": {
"type": "json" "$ref": "#/definitions/models.SimpleHostnameResponse"
} }
} }
} }
@ -297,14 +288,72 @@
} }
}, },
"definitions": { "definitions": {
"models.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"models.Hostname": {
"description": "Model of the Hostname as it is represented in the database",
"type": "object",
"properties": {
"Category": {
"description": "Category / Rule that was used when generating the hostname",
"type": "string",
"example": "notebook"
},
"Created_at": {
"description": "Creation Time of the entry",
"type": "string",
"example": "2024-01-16T12:53:59Z"
},
"Hostname": {
"description": "Generated hostname",
"type": "string",
"example": "ISEHENNB0009"
},
"Id": {
"description": "Internal ID of the Hostname within the database",
"type": "integer",
"example": 25
},
"Parameters": {
"description": "Parameter object of rule specific attributes, see rule.* models",
"type": "object",
"additionalProperties": true
}
}
},
"models.LoginCredentials": { "models.LoginCredentials": {
"description": "User account information used in the login process with Username and password", "description": "User account information used in the login process with Username and password",
"type": "object", "type": "object",
"properties": { "properties": {
"password": { "Password": {
"type": "string" "type": "string"
}, },
"username": { "Username": {
"type": "string"
}
}
},
"models.SimpleHostnameResponse": {
"description": "Model of the Hostname as returned by POST endpoint",
"type": "object",
"properties": {
"Hostname": {
"description": "Name of the newly generated host",
"type": "string"
}
}
},
"models.TokenResponse": {
"description": "Model returned after successful login",
"type": "object",
"properties": {
"token": {
"type": "string" "type": "string"
} }
} }

View File

@ -1,12 +1,54 @@
basePath: /api/v1 basePath: /api/v1
definitions: definitions:
models.ErrorResponse:
properties:
error:
type: string
type: object
models.Hostname:
description: Model of the Hostname as it is represented in the database
properties:
Category:
description: Category / Rule that was used when generating the hostname
example: notebook
type: string
Created_at:
description: Creation Time of the entry
example: "2024-01-16T12:53:59Z"
type: string
Hostname:
description: Generated hostname
example: ISEHENNB0009
type: string
Id:
description: Internal ID of the Hostname within the database
example: 25
type: integer
Parameters:
additionalProperties: true
description: Parameter object of rule specific attributes, see rule.* models
type: object
type: object
models.LoginCredentials: models.LoginCredentials:
description: User account information used in the login process with Username description: User account information used in the login process with Username
and password and password
properties: properties:
password: Password:
type: string type: string
username: Username:
type: string
type: object
models.SimpleHostnameResponse:
description: Model of the Hostname as returned by POST endpoint
properties:
Hostname:
description: Name of the newly generated host
type: string
type: object
models.TokenResponse:
description: Model returned after successful login
properties:
token:
type: string type: string
type: object type: object
rules.NotebookRuleInput: rules.NotebookRuleInput:
@ -43,9 +85,11 @@ paths:
- application/json - application/json
responses: responses:
"200": "200":
description: Hostname description: An array of responses
schema: schema:
type: json items:
$ref: '#/definitions/models.Hostname'
type: array
security: security:
- Bearer: [] - Bearer: []
summary: Return a list of hosts and their details filtered by category summary: Return a list of hosts and their details filtered by category
@ -72,7 +116,7 @@ paths:
"200": "200":
description: Hostname description: Hostname
schema: schema:
type: json $ref: '#/definitions/models.SimpleHostnameResponse'
security: security:
- Bearer: [] - Bearer: []
summary: Delete a hostname from the database summary: Delete a hostname from the database
@ -96,9 +140,9 @@ paths:
- application/json - application/json
responses: responses:
"200": "200":
description: Hostname description: A single response object
schema: schema:
type: json $ref: '#/definitions/models.Hostname'
security: security:
- Bearer: [] - Bearer: []
summary: Return a single hostname by Category and Name summary: Return a single hostname by Category and Name
@ -123,7 +167,7 @@ paths:
"200": "200":
description: Hostname description: Hostname
schema: schema:
type: string $ref: '#/definitions/models.SimpleHostnameResponse'
summary: Generate hostname for category "notebook" summary: Generate hostname for category "notebook"
tags: tags:
- Generating Hostnames - Generating Hostnames
@ -145,7 +189,7 @@ paths:
"200": "200":
description: Hostname description: Hostname
schema: schema:
type: string $ref: '#/definitions/models.SimpleHostnameResponse'
summary: Update hostname for category "notebook" summary: Update hostname for category "notebook"
tags: tags:
- Generating Hostnames - Generating Hostnames
@ -185,27 +229,19 @@ paths:
"200": "200":
description: Successfully authenticated, JWT token returned description: Successfully authenticated, JWT token returned
schema: schema:
additionalProperties: $ref: '#/definitions/models.TokenResponse'
type: string
type: object
"400": "400":
description: Invalid request body description: Invalid request body
schema: schema:
additionalProperties: $ref: '#/definitions/models.ErrorResponse'
type: string
type: object
"401": "401":
description: Invalid login credentials description: Invalid login credentials
schema: schema:
additionalProperties: $ref: '#/definitions/models.ErrorResponse'
type: string
type: object
"500": "500":
description: Internal server error description: Internal server error
schema: schema:
additionalProperties: $ref: '#/definitions/models.ErrorResponse'
type: string
type: object
summary: User login summary: User login
tags: tags:
- Authentication - Authentication

5
models/error.go Normal file
View File

@ -0,0 +1,5 @@
package models
type ErrorResponse struct {
Error string `json:"error"`
}

View File

@ -6,9 +6,16 @@ import "time"
// @Description Model of the Hostname as it // @Description Model of the Hostname as it
// @Description is represented in the database // @Description is represented in the database
type Hostname struct { type Hostname struct {
ID int `json:"id"` // Internal ID of the Hostname within the database ID int `json:"Id" example:"25"` // Internal ID of the Hostname within the database
Category string `json:"category"` // Category / Rule that was used when generating the hostname Category string `json:"Category" example:"notebook"` // Category / Rule that was used when generating the hostname
Hostname string `json:"hostname"` // Generated hostname Hostname string `json:"Hostname" example:"ISEHENNB0009"` // Generated hostname
Parameters map[string]interface{} `json:"parameters"` // Parameter object of rule specific attributes Parameters map[string]interface{} `json:"Parameters"` // Parameter object of rule specific attributes, see rule.* models
CreatedAt time.Time `json:"created_at"` // Creation Time of the entry CreatedAt time.Time `json:"Created_at" example:"2024-01-16T12:53:59Z"` // Creation Time of the entry
}
// SimpleHostnameResponse
// @Description Model of the Hostname as returned by
// @Description POST endpoint
type SimpleHostnameResponse struct {
Hostname string `json:"Hostname"` // Name of the newly generated host
} }

View File

@ -4,6 +4,12 @@ package models
// @Description User account information used in the login process // @Description User account information used in the login process
// @Description with Username and password // @Description with Username and password
type LoginCredentials struct { type LoginCredentials struct {
Username string `json:"username"` Username string `json:"Username"`
Password string `json:"password"` Password string `json:"Password"`
}
// JWT Token Response Model
// @Description Model returned after successful login
type TokenResponse struct {
Token string `json:"token"`
} }

View File

@ -32,7 +32,7 @@ func (br *BaseRule) baseInsert(category string, hostname string, paramsJSON []by
} }
func (br *BaseRule) baseUpdate(category string, oldhostname string, hostname string, paramsJSON []byte) error { func (br *BaseRule) baseUpdate(category string, oldhostname string, hostname string, paramsJSON []byte) error {
exists, err := db.HostnameExists(category, hostname) exists, err := db.HostnameExists(category, oldhostname)
if err != nil { if err != nil {
return fmt.Errorf("error checking existence of hostname: %v", err.Error()) return fmt.Errorf("error checking existence of hostname: %v", err.Error())
} }

View File

@ -66,27 +66,27 @@ 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 {string} string "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/notebook [post] // @Router /api/notebook [post]
func (nr *NotebookRule) Insert(category string, hostname string, paramsJSON []byte) error { func (nr *NotebookRule) Insert(category string, hostname string, paramsJSON []byte) error {
return nr.baseInsert(category, hostname, paramsJSON) return nr.baseInsert(category, hostname, paramsJSON)
} }
// @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 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 {string} string "Hostname" // @Success 200 {object} models.SimpleHostnameResponse "Hostname"
// @Router /api/notebook [put] // @Router /api/notebook [put]
func (nr *NotebookRule) Update(category string, oldhostname string, hostname string, paramsJSON []byte) error { func (nr *NotebookRule) Update(category string, oldhostname string, hostname string, paramsJSON []byte) error {
return nr.baseUpdate(category, oldhostname, hostname, paramsJSON) return nr.baseUpdate(category, oldhostname, hostname, paramsJSON)