mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-01-12 17:21:23 +01:00
feat: add reactions (#1074)
This commit is contained in:
44
app/app.go
Normal file
44
app/app.go
Normal 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
25
app/bootstrap.go
Normal 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
27
app/fakedata.go
Normal 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
80
app/flags.go
Normal 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
41
app/flags_test.go
Normal 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
72
app/migrate.go
Normal 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
21
app/migrate_test.go
Normal 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
52
app/routes.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user