Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
// https://github.com/pocketbase/pocketbase/blob/master/examples/base/main.go
package main

import (
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/apis"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/plugins/ghupdate"
	"github.com/pocketbase/pocketbase/plugins/jsvm"
	"github.com/pocketbase/pocketbase/plugins/migratecmd"
	"github.com/pocketbase/pocketbase/tools/hook"
)

func main() {
	app := pocketbase.New()

	// ---------------------------------------------------------------
	// Optional plugin flags:
	// ---------------------------------------------------------------

	var hooksDir string
	app.RootCmd.PersistentFlags().StringVar(
		&hooksDir,
		"hooksDir",
		"",
		"the directory with the JS app hooks",
	)

	var hooksWatch bool
	app.RootCmd.PersistentFlags().BoolVar(
		&hooksWatch,
		"hooksWatch",
		true,
		"auto restart the app on pb_hooks file change; it has no effect on Windows",
	)

	var hooksPool int
	app.RootCmd.PersistentFlags().IntVar(
		&hooksPool,
		"hooksPool",
		15,
		"the total prewarm goja.Runtime instances for the JS app hooks execution",
	)

	var migrationsDir string
	app.RootCmd.PersistentFlags().StringVar(
		&migrationsDir,
		"migrationsDir",
		"",
		"the directory with the user defined migrations",
	)

	var automigrate bool
	app.RootCmd.PersistentFlags().BoolVar(
		&automigrate,
		"automigrate",
		true,
		"enable/disable auto migrations",
	)

	var publicDir string
	app.RootCmd.PersistentFlags().StringVar(
		&publicDir,
		"publicDir",
		defaultPublicDir(),
		"the directory to serve static files",
	)

	var indexFallback bool
	app.RootCmd.PersistentFlags().BoolVar(
		&indexFallback,
		"indexFallback",
		true,
		"fallback the request to index.html on missing static path, e.g. when pretty urls are used with SPA",
	)

	app.RootCmd.ParseFlags(os.Args[1:])

	// ---------------------------------------------------------------
	// Plugins and hooks:
	// ---------------------------------------------------------------

	// load jsvm (pb_hooks and pb_migrations)
	jsvm.MustRegister(app, jsvm.Config{
		MigrationsDir: migrationsDir,
		HooksDir:      hooksDir,
		HooksWatch:    hooksWatch,
		HooksPoolSize: hooksPool,
	})

	// migrate command (with js templates)
	migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
		TemplateLang: migratecmd.TemplateLangJS,
		Automigrate:  automigrate,
		Dir:          migrationsDir,
	})

	// GitHub selfupdate
	ghupdate.MustRegister(app, app.RootCmd, ghupdate.Config{})

	registerHookNewOrg(app)
	onPricebookChanges(app)
	registerHookPreApprovedEmails(app)

	// Role-based Access Control
	app.OnRecordsListRequest().BindFunc(func(e *core.RecordsListRequestEvent) error {
		publicResources := []string{"currencies", "currencyRateFixed", "currencyRates", "organizations"}

		for _, resource := range publicResources {
			if resource == e.Collection.Name {
				return e.Next()
			}
		}

		return authRecordsListRequest(e, "list")
	})

	app.OnRecordViewRequest().BindFunc(func(e *core.RecordRequestEvent) error {
		return authRecordRequest(e, "view")
	})

	app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
		return authRecordRequest(e, "create")
	})

	app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
		return authRecordRequest(e, "update")
	})

	app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error {
		return authRecordRequest(e, "delete")
	})

	app.OnServe().BindFunc(func(se *core.ServeEvent) error {
		se.Router.POST("/api/join", func(e *core.RequestEvent) error {
			data := struct {
				OrgId  string `json:"orgId"`
				UserId string `json:"userId" `
			}{}
			if err := e.BindBody(&data); err != nil {
				return e.BadRequestError("Failed to read request data", err)
			}

			// Check if profile already exists for this user and org
			existingProfile, _ := e.App.FindFirstRecordByFilter(
				"profiles",
				"user = {:user} && org = {:org}",
				dbx.Params{"user": e.Auth.Id, "org": data.OrgId},
			)

			if existingProfile != nil {
				// Profile already exists - return friendly message without changing activeOrg
				return e.JSON(http.StatusOK, map[string]interface{}{
					"success": true,
					"message": "You are already a member of this organization",
				})
			}

			// Create new profile
			collection, err := e.App.FindCollectionByNameOrId("profiles")
			if err != nil {
				return err
			}

			record := core.NewRecord(collection)
			record.Set("user", e.Auth.Id)
			record.Set("org", data.OrgId)

			err = app.Save(record)

			if err != nil {
				return err
			}

			user, err := app.FindRecordById("users", e.Auth.Id)
			if err != nil {
				return err
			}

			// Get current profiles array
			currentProfiles := user.Get("profiles")
			var profileIds []string

			// Handle existing profiles (could be nil or empty)
			if currentProfiles != nil {
				switch v := currentProfiles.(type) {
				case []string:
					profileIds = v
				case []interface{}:
					for _, id := range v {
						if idStr, ok := id.(string); ok {
							profileIds = append(profileIds, idStr)
						}
					}
				}
			}

			// Add the new profile ID if not already present
			newProfileId := record.Id
			profileExists := false
			for _, id := range profileIds {
				if id == newProfileId {
					profileExists = true
					break
				}
			}

			if !profileExists {
				profileIds = append(profileIds, newProfileId)
			}

			// Update user with new profiles array and activeOrg
			user.Set("profiles", profileIds)
			user.Set("activeOrg", data.OrgId)
			err = app.Save(user)

			if err != nil {
				return err
			}

			return e.JSON(http.StatusOK, map[string]interface{}{
				"success": true,
				"message": "Successfully joined organization",
			})
		}).Bind(apis.RequireAuth())

		return se.Next()
	})

	// static route to serves files from the provided public dir
	// (if publicDir exists and the route path is not already defined)
	app.OnServe().Bind(&hook.Handler[*core.ServeEvent]{
		Func: func(e *core.ServeEvent) error {
			if !e.Router.HasRoute(http.MethodGet, "/{path...}") {
				e.Router.GET("/{path...}", apis.Static(os.DirFS(publicDir), indexFallback))
			}

			return e.Next()
		},
		Priority: 999, // execute as latest as possible to allow users to provide their own route
	})

	if err := app.Start(); err != nil {
		log.Fatal(err)
	}
}

// the default pb_public dir location is relative to the executable
func defaultPublicDir() string {
	if strings.HasPrefix(os.Args[0], os.TempDir()) {
		// most likely ran with go run
		return "./pb_public"
	}

	return filepath.Join(os.Args[0], "../pb_public")
}