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

gameap / gameap / 25960901307

16 May 2026 11:29AM UTC coverage: 76.887% (+0.2%) from 76.64%
25960901307

push

github

et-nik
audit logs tests

45395 of 59041 relevant lines covered (76.89%)

33895.16 hits per line

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

74.68
/internal/api/plugins/upload/install/handler.go
1
package install
2

3
import (
4
        "context"
5
        "log/slog"
6
        "net/http"
7
        "path"
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/files"
14
        "github.com/gameap/gameap/internal/plugin"
15
        "github.com/gameap/gameap/internal/repositories"
16
        "github.com/gameap/gameap/internal/services/plugininstall"
17
        "github.com/gameap/gameap/pkg/api"
18
        pkgplugin "github.com/gameap/gameap/pkg/plugin"
19
        "github.com/pkg/errors"
20
)
21

22
const extendedWriteDeadline = 5 * time.Minute
23

24
type LoaderManager interface {
25
        Load(ctx context.Context, wasmBytes []byte, config map[string]string, pluginID uint64) (*pkgplugin.LoadedPlugin, error)
26
        Unload(ctx context.Context, pluginID string) error
27
}
28

29
type Handler struct {
30
        manager     LoaderManager
31
        pluginRepo  repositories.PluginRepository
32
        fileManager files.FileManager
33
        loader      *plugin.Loader
34
        pluginsDir  string
35
        responder   base.Responder
36
        audit       audit.Logger
37
}
38

39
func NewHandler(
40
        manager LoaderManager,
41
        pluginRepo repositories.PluginRepository,
42
        fileManager files.FileManager,
43
        loader *plugin.Loader,
44
        pluginsDir string,
45
        responder base.Responder,
46
        auditLogger audit.Logger,
47
) *Handler {
7✔
48
        if auditLogger == nil {
12✔
49
                auditLogger = audit.NopLogger{}
5✔
50
        }
5✔
51

52
        return &Handler{
7✔
53
                manager:     manager,
7✔
54
                pluginRepo:  pluginRepo,
7✔
55
                fileManager: fileManager,
7✔
56
                loader:      loader,
7✔
57
                pluginsDir:  pluginsDir,
7✔
58
                responder:   responder,
7✔
59
                audit:       auditLogger,
7✔
60
        }
7✔
61
}
62

63
func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
7✔
64
        ctx := r.Context()
7✔
65

7✔
66
        rc := http.NewResponseController(rw)
7✔
67
        if err := rc.SetWriteDeadline(time.Now().Add(extendedWriteDeadline)); err != nil {
14✔
68
                slog.WarnContext(ctx, "failed to extend write deadline", slog.String("error", err.Error()))
7✔
69
        }
7✔
70

71
        wasmBytes, err := plugininstall.ReadWASMFromMultipart(rw, r)
7✔
72
        if err != nil {
8✔
73
                h.responder.WriteError(ctx, rw, err)
1✔
74

1✔
75
                return
1✔
76
        }
1✔
77

78
        if err := plugininstall.ValidateWASM(wasmBytes); err != nil {
7✔
79
                h.responder.WriteError(ctx, rw, err)
1✔
80

1✔
81
                return
1✔
82
        }
1✔
83

84
        loaded, err := h.manager.Load(ctx, wasmBytes, nil, 0)
5✔
85
        if err != nil {
6✔
86
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to load plugin for validation"))
1✔
87

1✔
88
                return
1✔
89
        }
1✔
90

91
        pluginID := pkgplugin.CompactPluginID(pkgplugin.ParsePluginID(loaded.Info.Id))
4✔
92
        dbID := pkgplugin.ParsePluginID(loaded.Info.Id)
4✔
93

4✔
94
        if err := h.manager.Unload(ctx, pluginID); err != nil {
4✔
95
                slog.WarnContext(ctx, "failed to unload temporary plugin",
×
96
                        slog.String("plugin_id", pluginID),
×
97
                        slog.String("error", err.Error()))
×
98
        }
×
99

100
        if err := plugininstall.CheckNotInstalled(ctx, h.pluginRepo, dbID); err != nil {
6✔
101
                h.responder.WriteError(ctx, rw, err)
2✔
102

2✔
103
                return
2✔
104
        }
2✔
105

106
        filename := strconv.FormatUint(uint64(dbID), 10) + ".wasm"
2✔
107
        pluginPath := path.Join(h.pluginsDir, filename)
2✔
108

2✔
109
        if err := h.fileManager.Write(ctx, pluginPath, wasmBytes); err != nil {
2✔
110
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to save plugin file"))
×
111

×
112
                return
×
113
        }
×
114

115
        pluginRecord := plugininstall.BuildPluginRecord(dbID, loaded, filename, "file://"+filename)
2✔
116

2✔
117
        if err := h.pluginRepo.Save(ctx, pluginRecord); err != nil {
2✔
118
                _ = h.fileManager.Delete(ctx, pluginPath)
×
119
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to save plugin record"))
×
120

×
121
                return
×
122
        }
×
123

124
        audit.SensitiveOp(ctx, h.audit, audit.EventPluginInstall, audit.CategoryPluginOp,
2✔
125
                "plugin", strconv.FormatUint(uint64(dbID), 10), "install",
2✔
126
                slog.String("plugin", loaded.Info.Id))
2✔
127

2✔
128
        if err := plugininstall.TryLoadPlugin(ctx, h.loader, h.pluginRepo, pluginRecord, filename); err != nil {
2✔
129
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
130
                        errors.WithMessage(err, "plugin installed but failed to load"),
×
131
                        http.StatusUnprocessableEntity,
×
132
                ))
×
133

×
134
                return
×
135
        }
×
136

137
        h.responder.Write(ctx, rw, newInstallResponse(pluginRecord))
2✔
138
}
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

© 2026 Coveralls, Inc