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

gameap / gameap / 25999900687

17 May 2026 07:02PM UTC coverage: 76.878% (+0.03%) from 76.852%
25999900687

push

github

et-nik
merge main into develop

283 of 349 new or added lines in 42 files covered. (81.09%)

1 existing line in 1 file now uncovered.

46386 of 60337 relevant lines covered (76.88%)

33171.97 hits per line

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

80.18
/internal/api/nodes/putnode/handler.go
1
package putnode
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "net/http"
7
        "path/filepath"
8
        "strconv"
9
        "time"
10

11
        "github.com/gameap/gameap/internal/api/base"
12
        "github.com/gameap/gameap/internal/audit"
13
        "github.com/gameap/gameap/internal/domain"
14
        "github.com/gameap/gameap/internal/files"
15
        "github.com/gameap/gameap/internal/filters"
16
        "github.com/gameap/gameap/internal/repositories"
17
        "github.com/gameap/gameap/pkg/api"
18
        "github.com/gameap/gameap/pkg/secret"
19
        "github.com/pkg/errors"
20
        "github.com/rs/xid"
21
)
22

23
const certificatesPath = "certs"
24

25
var (
26
        ErrFailedToSaveCertificate = errors.New("failed to save certificate")
27
        ErrFailedToUpdateNode      = errors.New("failed to update node")
28
        ErrNodeNotFound            = errors.New("node not found")
29
)
30

31
type Handler struct {
32
        repo        repositories.NodeRepository
33
        fileManager files.FileManager
34
        cipher      *secret.Cipher
35
        responder   base.Responder
36
        audit       audit.Logger
37
}
38

39
func NewHandler(
40
        repo repositories.NodeRepository,
41
        fileManager files.FileManager,
42
        cipher *secret.Cipher,
43
        responder base.Responder,
44
        auditLogger audit.Logger,
45
) *Handler {
19✔
46
        if auditLogger == nil {
36✔
47
                auditLogger = audit.NopLogger{}
17✔
48
        }
17✔
49

50
        return &Handler{
19✔
51
                repo:        repo,
19✔
52
                fileManager: fileManager,
19✔
53
                cipher:      cipher,
19✔
54
                responder:   responder,
19✔
55
                audit:       auditLogger,
19✔
56
        }
19✔
57
}
58

59
func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
19✔
60
        ctx := r.Context()
19✔
61

19✔
62
        input := api.NewInputReader(r)
19✔
63

19✔
64
        nodeID, err := input.ReadUint("id")
19✔
65
        if err != nil {
19✔
66
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
67
                        errors.WithMessage(err, "invalid node id"),
×
68
                        http.StatusBadRequest,
×
69
                ))
×
70

×
71
                return
×
72
        }
×
73

74
        node, err := h.findNode(ctx, nodeID)
19✔
75
        if err != nil {
22✔
76
                h.responder.WriteError(ctx, rw, err)
3✔
77

3✔
78
                return
3✔
79
        }
3✔
80

81
        var updateInput updateNodeInput
16✔
82
        err = json.NewDecoder(r.Body).Decode(&updateInput)
16✔
83
        if err != nil {
16✔
84
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
85
                        errors.WithMessage(err, "invalid request"),
×
86
                        http.StatusBadRequest,
×
87
                ))
×
88

×
89
                return
×
90
        }
×
91

92
        err = updateInput.Validate()
16✔
93
        if err != nil {
20✔
94
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
4✔
95
                        errors.WithMessage(err, "validation failed"),
4✔
96
                        http.StatusUnprocessableEntity,
4✔
97
                ))
4✔
98

4✔
99
                return
4✔
100
        }
4✔
101

102
        updatedNode, err := h.updateNode(ctx, node, &updateInput)
12✔
103
        if err != nil {
13✔
104
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to update node"))
1✔
105

1✔
106
                return
1✔
107
        }
1✔
108

109
        audit.SensitiveOp(ctx, h.audit, audit.EventNodeUpdate, audit.CategoryNodeOp,
11✔
110
                "node", strconv.FormatUint(uint64(nodeID), 10), "update")
11✔
111

11✔
112
        response := newNodeResponse(updatedNode)
11✔
113
        h.responder.Write(ctx, rw, response)
11✔
114
}
115

116
func (h *Handler) findNode(ctx context.Context, nodeID uint) (*domain.Node, error) {
19✔
117
        nodes, err := h.repo.Find(ctx, &filters.FindNode{
19✔
118
                IDs: []uint{nodeID},
19✔
119
        }, nil, &filters.Pagination{
19✔
120
                Limit:  1,
19✔
121
                Offset: 0,
19✔
122
        })
19✔
123
        if err != nil {
19✔
124
                return nil, errors.WithMessage(err, "failed to find node")
×
125
        }
×
126

127
        if len(nodes) == 0 {
22✔
128
                return nil, api.WrapHTTPError(ErrNodeNotFound, http.StatusNotFound)
3✔
129
        }
3✔
130

131
        return &nodes[0], nil
16✔
132
}
133

134
func (h *Handler) updateNode(ctx context.Context, node *domain.Node, input *updateNodeInput) (*domain.Node, error) {
12✔
135
        oldCertPath := node.GdaemonServerCert
12✔
136

12✔
137
        if input.GdaemonServerCert != nil && *input.GdaemonServerCert != "" {
16✔
138
                certXID := xid.New().String()
4✔
139
                certPath := filepath.Join(certificatesPath, certXID+".crt")
4✔
140

4✔
141
                if err := h.fileManager.Write(ctx, certPath, []byte(*input.GdaemonServerCert)); err != nil {
5✔
142
                        return nil, errors.WithMessage(ErrFailedToSaveCertificate, err.Error())
1✔
143
                }
1✔
144

145
                node.GdaemonServerCert = certPath
3✔
146
        }
147

148
        input.ApplyToNode(node)
11✔
149

11✔
150
        // Encrypt the SSH password at rest whenever the field is supplied in this
11✔
151
        // request. A request that omits gdaemon_password leaves the stored value
11✔
152
        // untouched; one that includes it is re-encrypted even if byte-identical,
11✔
153
        // so the ciphertext rotates (GCM uses a fresh nonce per call). Node PUTs
11✔
154
        // are rare admin operations, so this write churn is acceptable.
11✔
155
        if input.GdaemonPassword != nil && node.GdaemonPassword != nil {
14✔
156
                encrypted, err := h.cipher.Encrypt(*node.GdaemonPassword)
3✔
157
                if err != nil {
3✔
NEW
158
                        return nil, errors.WithMessage(err, "failed to encrypt gdaemon password")
×
NEW
159
                }
×
160

161
                node.GdaemonPassword = &encrypted
3✔
162
        }
163

164
        now := time.Now()
11✔
165
        node.UpdatedAt = &now
11✔
166

11✔
167
        if err := h.repo.Save(ctx, node); err != nil {
11✔
168
                if input.GdaemonServerCert != nil && *input.GdaemonServerCert != "" {
×
169
                        _ = h.fileManager.Delete(ctx, node.GdaemonServerCert)
×
170
                }
×
171

172
                return nil, errors.WithMessage(ErrFailedToUpdateNode, err.Error())
×
173
        }
174

175
        if input.GdaemonServerCert != nil && *input.GdaemonServerCert != "" && oldCertPath != "" {
14✔
176
                _ = h.fileManager.Delete(ctx, oldCertPath)
3✔
177
        }
3✔
178

179
        return node, nil
11✔
180
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc