• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

heathcliff26 / go-wol / 17074952134

19 Aug 2025 03:53PM UTC coverage: 91.182% (-0.5%) from 91.71%
17074952134

Pull #86

github

web-flow
Merge 836801226 into 9946d24e0
Pull Request #86: Add address to host attributes

84 of 95 new or added lines in 8 files covered. (88.42%)

6 existing lines in 1 file now uncovered.

910 of 998 relevant lines covered (91.18%)

54.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.6
/pkg/server/api/v1/api.go
1
package v1
2

3
//        @title                        go-wol API
4
//        @version                1.0
5
//        @description        Manage known hosts and send magic packets.
6

7
//        @license.name        Apache 2.0
8
//        @license.url        http://www.apache.org/licenses/LICENSE-2.0.html
9

10
//        @BasePath        /api/v1
11
//        @accept                json
12
//        @produce        json
13

14
import (
15
        "encoding/json"
16
        "log/slog"
17
        "net/http"
18

19
        "github.com/heathcliff26/go-wol/pkg/server/storage"
20
        "github.com/heathcliff26/go-wol/pkg/server/storage/types"
21
        "github.com/heathcliff26/go-wol/pkg/utils"
22
        "github.com/heathcliff26/go-wol/pkg/wol"
23
)
24

25
type Response struct {
26
        Status string `json:"status"`
27
        Reason string `json:"reason"`
28
}
29

30
type apiHandler struct {
31
        storage *storage.Storage
32
}
33

34
func NewRouter(storage *storage.Storage) *http.ServeMux {
3✔
35
        handler := &apiHandler{
3✔
36
                storage: storage,
3✔
37
        }
3✔
38

3✔
39
        router := http.NewServeMux()
3✔
40
        router.HandleFunc("GET /wake/{macAddr}", WakeHandler)
3✔
41
        router.HandleFunc("GET /hosts", handler.GetHostsHandler)
3✔
42
        router.HandleFunc("PUT /hosts", handler.AddHostHandler)
3✔
43
        router.HandleFunc("DELETE /hosts/{macAddr}", handler.RemoveHostHandler)
3✔
44
        return router
3✔
45
}
3✔
46

47
// @Summary                Wake up host
48
// @Description        Send a magic packet to the specified MAC address
49
//
50
// @Produce                json
51
// @Param                        macAddr        path                string                true        "MAC address of the host"
52
// @Success                200                {object}        Response        "ok"
53
// @Failure                400                {object}        Response        "Invalid MAC address"
54
// @Failure                500                {object}        Response        "Failed to send magic packet"
55
// @Router                        /wake/{macAddr} [get]
56
func WakeHandler(res http.ResponseWriter, req *http.Request) {
3✔
57
        macAddr := req.PathValue("macAddr")
3✔
58

3✔
59
        packet, err := wol.CreatePacket(macAddr)
3✔
60
        if err != nil {
5✔
61
                slog.Info("Client sent invalid MAC address", slog.String("mac", macAddr), slog.Any("error", err))
2✔
62
                res.WriteHeader(http.StatusBadRequest)
2✔
63
                sendResponse(res, "Invalid MAC address")
2✔
64
                return
2✔
65
        }
2✔
66

67
        err = packet.Send("")
1✔
68
        if err != nil {
1✔
69
                slog.Info("Failed to send magic packet", slog.String("mac", macAddr), slog.Any("error", err))
×
70
                res.WriteHeader(http.StatusInternalServerError)
×
71
                sendResponse(res, "Failed to send magic packet")
×
72
                return
×
73
        }
×
74

75
        slog.Info("Sent magic packet", slog.String("mac", macAddr))
1✔
76
        sendResponse(res, "")
1✔
77
}
78

79
// @Summary                Get hosts
80
// @Description        Fetch all known hosts
81
//
82
// @Produce                json
83
// @Success                200        {object}        []types.Host        "List of all known hosts"
84
// @Failure                500        {object}        Response                "Failed to retrieve hosts from storage"
85
// @Router                        /hosts [get]
86
func (h *apiHandler) GetHostsHandler(res http.ResponseWriter, req *http.Request) {
2✔
87
        hosts, err := h.storage.GetHosts()
2✔
88
        if err != nil {
3✔
89
                slog.Error("Failed to fetch hosts", "error", err)
1✔
90
                res.WriteHeader(http.StatusInternalServerError)
1✔
91
                sendResponse(res, "Failed to fetch hosts")
1✔
92
                return
1✔
93
        }
1✔
94

95
        sendJSONResponse(res, hosts)
1✔
96
}
97

98
// @Summary                Add new host
99
// @Description        Add a new host to the known hosts
100
//
101
// @Accept                        json
102
// @Produce                json
103
// @Param                        macAddr        body                string                true        "MAC address of the host"
104
// @Param                        name        body                string                true        "Name of the host"
105
// @Param                        address        body                string                false        "IP or DNS address of the host, used for checking if the host is online"
106
// @Success                200                {object}        Response        "ok"
107
// @Failure                400                {object}        Response        "Invalid MAC address or hostname"
108
// @Failure                403                {object}        Response        "Storage is readonly"
109
// @Failure                500                {object}        Response        "Failed to add host"
110
// @Router                        /hosts [put]
111
func (h *apiHandler) AddHostHandler(res http.ResponseWriter, req *http.Request) {
5✔
112
        var host types.Host
5✔
113
        err := json.NewDecoder(req.Body).Decode(&host)
5✔
114
        if err != nil {
5✔
NEW
115
                slog.Debug("Client sent invalid host json", "error", err)
×
NEW
116
                res.WriteHeader(http.StatusBadRequest)
×
NEW
117
                sendResponse(res, "Request body must be a valid host JSON object")
×
NEW
UNCOV
118
                return
×
NEW
UNCOV
119
        }
×
120

121
        if h.storage.Readonly() {
6✔
122
                slog.Debug("Client tried to add host while storage is readonly")
1✔
123
                res.WriteHeader(http.StatusForbidden)
1✔
124
                sendResponse(res, "Storage is readonly")
1✔
125
                return
1✔
126
        }
1✔
127

128
        if !utils.ValidateMACAddress(host.MAC) {
5✔
129
                slog.Debug("Client send invalid MAC address", slog.String("mac", host.MAC))
1✔
130
                res.WriteHeader(http.StatusBadRequest)
1✔
131
                sendResponse(res, "Invalid MAC address")
1✔
132
                return
1✔
133
        }
1✔
134

135
        if !utils.ValidateHostname(host.Name) {
4✔
136
                slog.Debug("Client send invalid hostname", slog.String("name", host.Name))
1✔
137
                res.WriteHeader(http.StatusBadRequest)
1✔
138
                sendResponse(res, "Invalid hostname")
1✔
139
                return
1✔
140
        }
1✔
141

142
        err = h.storage.AddHost(host)
2✔
143
        if err != nil {
3✔
144
                slog.Error("Failed to add host", "host", host, "error", err)
1✔
145
                res.WriteHeader(http.StatusInternalServerError)
1✔
146
                sendResponse(res, "Failed to add host")
1✔
147
                return
1✔
148
        }
1✔
149

150
        slog.Info("Added host", "host", host)
1✔
151
        sendResponse(res, "")
1✔
152
}
153

154
// @Summary                Remove host
155
// @Description        Remove a host from the list of known hosts
156
//
157
// @Produce                json
158
// @Param                        macAddr        path                string                true        "MAC address of the host"
159
// @Success                200                {object}        Response        "ok"
160
// @Failure                400                {object}        Response        "Invalid MAC address"
161
// @Failure                403                {object}        Response        "Storage is readonly"
162
// @Failure                500                {object}        Response        "Failed to remove host"
163
// @Router                        /hosts/{macAddr} [delete]
164
func (h *apiHandler) RemoveHostHandler(res http.ResponseWriter, req *http.Request) {
4✔
165
        macAddr := req.PathValue("macAddr")
4✔
166

4✔
167
        if h.storage.Readonly() {
5✔
168
                slog.Debug("Client tried to remove host while storage is readonly")
1✔
169
                res.WriteHeader(http.StatusForbidden)
1✔
170
                sendResponse(res, "Storage is readonly")
1✔
171
                return
1✔
172
        }
1✔
173

174
        if !utils.ValidateMACAddress(macAddr) {
4✔
175
                slog.Debug("Client send invalid MAC address", slog.String("mac", macAddr))
1✔
176
                res.WriteHeader(http.StatusBadRequest)
1✔
177
                sendResponse(res, "Invalid MAC address")
1✔
178
                return
1✔
179
        }
1✔
180

181
        err := h.storage.RemoveHost(macAddr)
2✔
182
        if err != nil {
3✔
183
                slog.Error("Failed to remove host", "mac", macAddr, "error", err)
1✔
184
                res.WriteHeader(http.StatusInternalServerError)
1✔
185
                sendResponse(res, "Failed to remove host")
1✔
186
                return
1✔
187
        }
1✔
188

189
        slog.Info("Removed host", slog.String("mac", macAddr))
1✔
190
        sendResponse(res, "")
1✔
191
}
192

193
func sendResponse(rw http.ResponseWriter, reason string) {
13✔
194
        response := Response{
13✔
195
                Status: "error",
13✔
196
                Reason: reason,
13✔
197
        }
13✔
198
        if reason == "" {
16✔
199
                response.Status = "ok"
3✔
200
        }
3✔
201

202
        sendJSONResponse(rw, response)
13✔
203
}
204

205
// Send an arbitrary JSON Object to the client
206
func sendJSONResponse(rw http.ResponseWriter, data any) {
14✔
207
        b, err := json.MarshalIndent(data, "", "  ")
14✔
208
        if err != nil {
14✔
209
                slog.Error("Failed to create Response", "err", err)
×
UNCOV
210
                return
×
UNCOV
211
        }
×
212

213
        rw.Header().Set("Content-Type", "application/json")
14✔
214

14✔
215
        _, err = rw.Write(b)
14✔
216
        if err != nil {
14✔
UNCOV
217
                slog.Error("Failed to send response to client", "err", err)
×
UNCOV
218
        }
×
219
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc