feat: add reactions (#1074)

This commit is contained in:
Jonas Plum
2024-07-20 06:39:02 +02:00
committed by GitHub
parent 82ad50d228
commit e2c8f1d223
78 changed files with 3270 additions and 257 deletions

44
app/app.go Normal file
View File

@@ -0,0 +1,44 @@
package app
import (
"os"
"strings"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/SecurityBrewery/catalyst/migrations"
"github.com/SecurityBrewery/catalyst/reaction"
"github.com/SecurityBrewery/catalyst/webhook"
)
func init() {
migrations.Register()
}
func App(dir string) *pocketbase.PocketBase {
app := pocketbase.NewWithConfig(pocketbase.Config{
DefaultDev: dev(),
DefaultDataDir: dir,
})
BindHooks(app)
// Register additional commands
app.RootCmd.AddCommand(bootstrapCmd(app))
app.RootCmd.AddCommand(fakeDataCmd(app))
app.RootCmd.AddCommand(setFeatureFlagsCmd(app))
return app
}
func BindHooks(app core.App) {
webhook.BindHooks(app)
reaction.BindHooks(app)
app.OnBeforeServe().Add(addRoutes())
}
func dev() bool {
return strings.HasPrefix(os.Args[0], os.TempDir())
}

25
app/bootstrap.go Normal file
View File

@@ -0,0 +1,25 @@
package app
import (
"github.com/pocketbase/pocketbase/core"
"github.com/spf13/cobra"
)
func Bootstrap(app core.App) error {
if err := app.Bootstrap(); err != nil {
return err
}
return MigrateDBs(app)
}
func bootstrapCmd(app core.App) *cobra.Command {
return &cobra.Command{
Use: "bootstrap",
Run: func(_ *cobra.Command, _ []string) {
if err := Bootstrap(app); err != nil {
app.Logger().Error(err.Error())
}
},
}
}

27
app/fakedata.go Normal file
View File

@@ -0,0 +1,27 @@
package app
import (
"github.com/pocketbase/pocketbase/core"
"github.com/spf13/cobra"
"github.com/SecurityBrewery/catalyst/fakedata"
)
func fakeDataCmd(app core.App) *cobra.Command {
var userCount, ticketCount int
cmd := &cobra.Command{
Use: "fake-data",
Run: func(_ *cobra.Command, _ []string) {
if err := fakedata.Generate(app, userCount, ticketCount); err != nil {
app.Logger().Error(err.Error())
}
},
}
cmd.PersistentFlags().IntVar(&userCount, "users", 10, "Number of users to generate")
cmd.PersistentFlags().IntVar(&ticketCount, "tickets", 100, "Number of tickets to generate")
return cmd
}

80
app/flags.go Normal file
View File

@@ -0,0 +1,80 @@
package app
import (
"slices"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/spf13/cobra"
"github.com/SecurityBrewery/catalyst/migrations"
)
func Flags(app core.App) ([]string, error) {
records, err := app.Dao().FindRecordsByExpr(migrations.FeatureCollectionName)
if err != nil {
return nil, err
}
var flags []string
for _, r := range records {
flags = append(flags, r.GetString("name"))
}
return flags, nil
}
func SetFlags(app core.App, args []string) error {
featureCollection, err := app.Dao().FindCollectionByNameOrId(migrations.FeatureCollectionName)
if err != nil {
return err
}
featureRecords, err := app.Dao().FindRecordsByExpr(migrations.FeatureCollectionName)
if err != nil {
return err
}
var existingFlags []string
for _, featureRecord := range featureRecords {
// remove feature flags that are not in the args
if !slices.Contains(args, featureRecord.GetString("name")) {
if err := app.Dao().DeleteRecord(featureRecord); err != nil {
return err
}
continue
}
existingFlags = append(existingFlags, featureRecord.GetString("name"))
}
for _, arg := range args {
if slices.Contains(existingFlags, arg) {
continue
}
// add feature flags that are not in the args
record := models.NewRecord(featureCollection)
record.Set("name", arg)
if err := app.Dao().SaveRecord(record); err != nil {
return err
}
}
return nil
}
func setFeatureFlagsCmd(app core.App) *cobra.Command {
return &cobra.Command{
Use: "set-feature-flags",
Run: func(_ *cobra.Command, args []string) {
if err := SetFlags(app, args); err != nil {
app.Logger().Error(err.Error())
}
},
}
}

41
app/flags_test.go Normal file
View File

@@ -0,0 +1,41 @@
package app_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app"
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
)
func Test_flags(t *testing.T) {
catalystApp, cleanup := catalystTesting.App(t)
defer cleanup()
got, err := app.Flags(catalystApp)
require.NoError(t, err)
want := []string{}
assert.ElementsMatch(t, want, got)
}
func Test_setFlags(t *testing.T) {
catalystApp, cleanup := catalystTesting.App(t)
defer cleanup()
require.NoError(t, app.SetFlags(catalystApp, []string{"test"}))
got, err := app.Flags(catalystApp)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"test"}, got)
require.NoError(t, app.SetFlags(catalystApp, []string{"test2"}))
got, err = app.Flags(catalystApp)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"test2"}, got)
}

72
app/migrate.go Normal file
View File

@@ -0,0 +1,72 @@
package app
import (
"strings"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/migrations/logs"
"github.com/pocketbase/pocketbase/tools/migrate"
)
type migration struct {
db *dbx.DB
migrations migrate.MigrationsList
}
func MigrateDBs(app core.App) error {
for _, m := range []migration{
{db: app.DB(), migrations: migrations.AppMigrations},
{db: app.LogsDB(), migrations: logs.LogsMigrations},
} {
runner, err := migrate.NewRunner(m.db, m.migrations)
if err != nil {
return err
}
if _, err := runner.Up(); err != nil {
return err
}
}
return nil
}
// this fix ignores some errors that come from upstream migrations.
var ignoreErrors = []string{
"1673167670_multi_match_migrate",
"1660821103_add_user_ip_column",
}
func isIgnored(err error) bool {
for _, ignore := range ignoreErrors {
if strings.Contains(err.Error(), ignore) {
return true
}
}
return false
}
func MigrateDBsDown(app core.App) error {
for _, m := range []migration{
{db: app.DB(), migrations: migrations.AppMigrations},
{db: app.LogsDB(), migrations: logs.LogsMigrations},
} {
runner, err := migrate.NewRunner(m.db, m.migrations)
if err != nil {
return err
}
if _, err := runner.Down(len(m.migrations.Items())); err != nil {
if isIgnored(err) {
continue
}
return err
}
}
return nil
}

21
app/migrate_test.go Normal file
View File

@@ -0,0 +1,21 @@
package app_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app"
"github.com/SecurityBrewery/catalyst/migrations"
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
)
func Test_MigrateDBsDown(t *testing.T) {
catalystApp, cleanup := catalystTesting.App(t)
defer cleanup()
_, err := catalystApp.Dao().FindCollectionByNameOrId(migrations.ReactionCollectionName)
require.NoError(t, err)
require.NoError(t, app.MigrateDBsDown(catalystApp))
}

52
app/routes.go Normal file
View File

@@ -0,0 +1,52 @@
package app
import (
"net/http"
"net/http/httputil"
"net/url"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/SecurityBrewery/catalyst/ui"
)
func addRoutes() func(*core.ServeEvent) error {
return func(e *core.ServeEvent) error {
e.Router.GET("/", func(c echo.Context) error {
return c.Redirect(http.StatusFound, "/ui/")
})
e.Router.GET("/ui/*", staticFiles())
e.Router.GET("/api/config", func(c echo.Context) error {
flags, err := Flags(e.App)
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]any{
"flags": flags,
})
})
return nil
}
}
func staticFiles() func(echo.Context) error {
return func(c echo.Context) error {
if dev() {
u, _ := url.Parse("http://localhost:3000/")
proxy := httputil.NewSingleHostReverseProxy(u)
c.Request().Host = c.Request().URL.Host
proxy.ServeHTTP(c.Response(), c.Request())
return nil
}
return apis.StaticDirectoryHandler(ui.UI(), true)(c)
}
}