feat: return json responses

This commit is contained in:
2025-05-04 17:25:29 +02:00
parent d15ec1f8cb
commit 666c9c9eb8
2 changed files with 209 additions and 43 deletions

View File

@@ -15,6 +15,14 @@ components:
in: header in: header
name: X-API-Token name: X-API-Token
description: API token for authentication description: API token for authentication
schemas:
Error:
type: object
properties:
error:
type: string
description: Error message
example: "some error message"
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
@@ -36,6 +44,12 @@ paths:
status: status:
type: string type: string
example: "healthy" example: "healthy"
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/network/ap: /network/ap:
post: post:
@@ -78,10 +92,22 @@ paths:
example: "7d706027-727c-4d4c-a816-f0e1b99db8ab" example: "7d706027-727c-4d4c-a816-f0e1b99db8ab"
'400': '400':
description: Invalid request payload description: Invalid request payload
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/network/interface/{interface}: /network/interface/{interface}:
put: put:
@@ -111,12 +137,32 @@ paths:
responses: responses:
'200': '200':
description: Network interface brought up successfully description: Network interface brought up successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
'400': '400':
description: Invalid request payload description: Invalid request payload
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete: delete:
summary: Deactivate network connection summary: Deactivate network connection
description: Deactivates the specified network connection description: Deactivates the specified network connection
@@ -131,12 +177,32 @@ paths:
responses: responses:
'200': '200':
description: Network interface brought down successfully description: Network interface brought down successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
'400': '400':
description: Invalid request payload description: Invalid request payload
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/network/connection/{uuid}: /network/connection/{uuid}:
delete: delete:
@@ -153,12 +219,32 @@ paths:
responses: responses:
'200': '200':
description: Connection profile removed successfully description: Connection profile removed successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
'400': '400':
description: Invalid request payload description: Invalid request payload
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/hostname: /hostname:
get: get:
@@ -168,15 +254,26 @@ paths:
'200': '200':
description: Hostname retrieved successfully description: Hostname retrieved successfully
content: content:
text/plain: application/json:
schema: schema:
type: string type: object
description: Current hostname properties:
example: "MyHostname" hostname:
type: string
description: Current hostname
example: "MyHostname"
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post: post:
summary: Set system hostname summary: Set system hostname
description: Sets a new system hostname description: Sets a new system hostname
@@ -196,12 +293,32 @@ paths:
responses: responses:
'200': '200':
description: Hostname set successfully description: Hostname set successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
'400': '400':
description: Invalid request payload description: Invalid request payload
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{user}/keys: /users/{user}/keys:
post: post:
@@ -232,17 +349,32 @@ paths:
'200': '200':
description: Key added successfully description: Key added successfully
content: content:
text/plain: application/json:
schema: schema:
type: string type: object
description: Fingerprint of the added key properties:
example: "SHA256:abcdef1234567890..." fingerprint:
type: string
description: Fingerprint of the added key
example: "SHA256:abcdef1234567890..."
'400': '400':
description: Invalid request payload or SSH key format description: Invalid request payload or SSH key format
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{user}/keys/{fingerprint}: /users/{user}/keys/{fingerprint}:
delete: delete:
@@ -261,15 +393,34 @@ paths:
required: true required: true
schema: schema:
type: string type: string
format: string
description: URL-safe Base64 encoded fingerprint of the key to remove description: URL-safe Base64 encoded fingerprint of the key to remove
example: "U0hBMjU2OmFiY2RlZjEyMzQ1Njc4OTAuLi4=" example: "U0hBMjU2OmFiY2RlZjEyMzQ1Njc4OTAuLi4="
responses: responses:
'200': '200':
description: Key removed successfully description: Key removed successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
'400': '400':
description: Invalid request payload or fingerprint description: Invalid request payload or fingerprint
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401': '401':
description: Unauthorized - invalid or missing API token description: Unauthorized - invalid or missing API token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500': '500':
description: Internal server error description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

View File

@@ -11,10 +11,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
const (
NETWORK_CONNECTION_UUID = "7d706027-727c-4d4c-a816-f0e1b99db8ab"
)
type configureAPRequest struct { type configureAPRequest struct {
Interface string `json:"interface"` Interface string `json:"interface"`
SSID string `json:"ssid"` SSID string `json:"ssid"`
@@ -34,15 +30,25 @@ type authorizedKeyRequest struct {
PubKey string `json:"pubkey"` PubKey string `json:"pubkey"`
} }
type errorResponse struct {
Error string `json:"error"`
}
func writeError(w http.ResponseWriter, message string, code int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(errorResponse{Error: message})
}
func HandleConfigureAP(w http.ResponseWriter, r *http.Request) { func HandleConfigureAP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
var req configureAPRequest var req configureAPRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) writeError(w, err.Error(), http.StatusBadRequest)
return return
} }
@@ -50,7 +56,7 @@ func HandleConfigureAP(w http.ResponseWriter, r *http.Request) {
uuid, err := network.ConfigureAP(req.Interface, req.SSID, req.Password) uuid, err := network.ConfigureAP(req.Interface, req.SSID, req.Password)
if err != nil { if err != nil {
log.Printf("Failed to configure access point on interface %s: %v", req.Interface, err) log.Printf("Failed to configure access point on interface %s: %v", req.Interface, err)
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
log.Printf("Successfully configured access point on interface %s with UUID %s", req.Interface, uuid) log.Printf("Successfully configured access point on interface %s with UUID %s", req.Interface, uuid)
@@ -64,19 +70,20 @@ func HandleConfigureAP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil { if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Failed to encode response: %v", err) log.Printf("Failed to encode response: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
} }
func HandleNetworkUp(w http.ResponseWriter, r *http.Request) { func HandleNetworkUp(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut { if r.Method != http.MethodPut {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
var req networkUpRequest var req networkUpRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) writeError(w, err.Error(), http.StatusBadRequest)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
@@ -85,84 +92,90 @@ func HandleNetworkUp(w http.ResponseWriter, r *http.Request) {
log.Printf("Bringing up network interface %s with UUID %s", iface, req.UUID) log.Printf("Bringing up network interface %s with UUID %s", iface, req.UUID)
if err := network.Up(iface, req.UUID); err != nil { if err := network.Up(iface, req.UUID); err != nil {
log.Printf("Failed to bring up network interface %s: %v", iface, err) log.Printf("Failed to bring up network interface %s: %v", iface, err)
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
log.Printf("Successfully brought up network interface %s", iface) log.Printf("Successfully brought up network interface %s", iface)
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} }
func HandleNetworkDown(w http.ResponseWriter, r *http.Request) { func HandleNetworkDown(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete { if r.Method != http.MethodDelete {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
iface := vars["interface"] iface := vars["interface"]
if err := network.Down(iface); err != nil { if err := network.Down(iface); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} }
func HandleNetworkRemove(w http.ResponseWriter, r *http.Request) { func HandleNetworkRemove(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete { if r.Method != http.MethodDelete {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
uuid := vars["uuid"] uuid := vars["uuid"]
if err := network.Remove(uuid); err != nil { if err := network.Remove(uuid); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} }
func HandleGetHostname(w http.ResponseWriter, r *http.Request) { func HandleGetHostname(w http.ResponseWriter, r *http.Request) {
hostname, err := network.GetHostname() hostname, err := network.GetHostname()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.Write([]byte(hostname))
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"hostname": hostname})
} }
func HandleSetHostname(w http.ResponseWriter, r *http.Request) { func HandleSetHostname(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
var req setHostnameRequest var req setHostnameRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) writeError(w, err.Error(), http.StatusBadRequest)
return return
} }
if err := network.SetHostname(req.Hostname); err != nil { if err := network.SetHostname(req.Hostname); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} }
func HandleAddAuthorizedKey(w http.ResponseWriter, r *http.Request) { func HandleAddAuthorizedKey(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
var req authorizedKeyRequest var req authorizedKeyRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) writeError(w, err.Error(), http.StatusBadRequest)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
@@ -170,42 +183,44 @@ func HandleAddAuthorizedKey(w http.ResponseWriter, r *http.Request) {
fingerprint, err := user.AddAuthorizedKey(username, req.PubKey) fingerprint, err := user.AddAuthorizedKey(username, req.PubKey)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.Write([]byte(fingerprint)) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"fingerprint": fingerprint})
} }
func HandleRemoveAuthorizedKey(w http.ResponseWriter, r *http.Request) { func HandleRemoveAuthorizedKey(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete { if r.Method != http.MethodDelete {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
fingerprint := vars["fingerprint"] fingerprint := vars["fingerprint"]
if fingerprint == "" { if fingerprint == "" {
http.Error(w, "fingerprint parameter is required", http.StatusBadRequest) writeError(w, "fingerprint parameter is required", http.StatusBadRequest)
return return
} }
username := vars["user"] username := vars["user"]
if username == "" { if username == "" {
http.Error(w, "user parameter is required", http.StatusBadRequest) writeError(w, "user parameter is required", http.StatusBadRequest)
return return
} }
fingerprintBytes, err := base64.RawURLEncoding.DecodeString(fingerprint) fingerprintBytes, err := base64.RawURLEncoding.DecodeString(fingerprint)
if err != nil { if err != nil {
http.Error(w, "invalid fingerprint base64", http.StatusBadRequest) writeError(w, "invalid fingerprint base64", http.StatusBadRequest)
return return
} }
if err := user.RemoveAuthorizedKey(username, string(fingerprintBytes)); err != nil { if err := user.RemoveAuthorizedKey(username, string(fingerprintBytes)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) writeError(w, err.Error(), http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} }