{"id":50594702,"url":"https://github.com/wardvisual/im-business-suite","last_synced_at":"2026-06-05T13:01:28.488Z","repository":{"id":350909123,"uuid":"1078302158","full_name":"wardvisual/im-business-suite","owner":"wardvisual","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-12T17:36:14.000Z","size":1324,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-12T19:20:36.859Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wardvisual.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-17T14:14:25.000Z","updated_at":"2026-04-12T17:36:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wardvisual/im-business-suite","commit_stats":null,"previous_names":["wardvisual/im-business-suite"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/wardvisual/im-business-suite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardvisual%2Fim-business-suite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardvisual%2Fim-business-suite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardvisual%2Fim-business-suite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardvisual%2Fim-business-suite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wardvisual","download_url":"https://codeload.github.com/wardvisual/im-business-suite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardvisual%2Fim-business-suite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33942436,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-05T13:01:27.933Z","updated_at":"2026-06-05T13:01:28.477Z","avatar_url":"https://github.com/wardvisual.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go Business Suite — Comprehensive Boilerplate\n\n\u003e A production-minded starter codebase for a multi-tenant B2B \"suite\" web app written in **Go**. This repo is designed as a single codebase that starts small (\"Suite Lite\") and scales to a full platform: auth, RBAC, multi-tenancy, modules, API versioning, background jobs, CI/CD, Docker, monitoring hooks, and billing hooks.\n\n---\n\n## Goals\n\n* Provide a clean, modular structure for adding modules (CRM, Invoicing, Projects, Analytics).\n* Include baked-in multi-tenant patterns and role-based access control.\n* Include practical infrastructure config: Docker, docker-compose, CI workflow, and production hints.\n* Keep `README` friendly so GitHub Copilot / automation can scaffold the rest.\n\n---\n\n## Folder layout (recommended)\n\n```\n/go-business-suite\n├── cmd/\n│   └── api/                  # main program entry for API\n│       └── main.go\n├── internal/\n│   ├── config/               # configuration loader (env, file)\n│   ├── server/               # HTTP server \u0026 router setup\n│   ├── db/                   # DB connection, migrations wrapper\n│   ├── auth/                 # authentication, JWT, password hashing\n│   ├── middleware/           # request middleware (tenant, rbac, logging)\n│   ├── modules/\n│   │   ├── crm/              # sample module (contacts, leads)\n│   │   └── invoicing/        # sample invoice module skeleton\n│   ├── jobs/                 # background jobs queue workers\n│   └── telemetry/            # metrics, tracing, logging setup\n├── migrations/               # SQL migration files (golang-migrate)\n├── scripts/                  # helper scripts (generate, lint, db seed)\n├── deployments/              # k8s / helm / svc manifests (optional)\n├── .github/workflows/        # CI pipeline\n├── docker-compose.yml\n├── Dockerfile\n├── Makefile\n├── go.mod\n├── go.sum\n├── .env.example\n└── README.md\n```\n\n---\n\n## Key design decisions (technical summary)\n\n1. **HTTP framework**: `gin-gonic/gin` for minimal ceremony + performance.\n2. **DB**: `PostgreSQL` as primary persistent store.\n3. **ORM / Query**: `GORM` initially for rapid development. Move to `sqlc` if you want stronger typed SQL later.\n4. **Migrations**: `golang-migrate/migrate`.\n5. **Auth**: JWT access tokens with refresh tokens persisted in DB, password hashing with `bcrypt`.\n6. **Multi-tenancy**: Row-level tenancy using `organization_id` on business tables + middleware that sets `current_tenant` from auth token or subdomain.\n7. **RBAC**: Role-based access control (Owner, Admin, Member, Guest) implemented as middleware with permission checks on handlers.\n8. **Background jobs**: Redis-based queue (e.g., `asynq`) for email, invoices, exports.\n9. **Storage**: S3-compatible (MinIO/AWS S3) for file uploads; UploadCare or direct S3.\n10. **Payments**: Stripe webhooks handler skeleton in `modules/billing`.\n11. **Observability**: Sentry for errors + Prometheus metrics endpoint.\n12. **CI/CD**: Build/test on push via GitHub Actions, publish Docker image to registry.\n\n---\n\n## .env.example\n\n```env\n# App\nAPP_ENV=development\nAPP_PORT=8080\nAPP_HOST=0.0.0.0\nAPP_BASE_URL=http://localhost:8080\nJWT_SECRET=replace_with_secure_random\nJWT_EXPIRATION_MINUTES=60\nREFRESH_TOKEN_EXPIRY_DAYS=14\n\n# Database (Postgres)\nDATABASE_URL=postgres://postgres:postgres@db:5432/business_suite?sslmode=disable\n\n# Redis (jobs \u0026 cache)\nREDIS_URL=redis://redis:6379/0\n\n# S3 / Object storage\nS3_ENDPOINT=http://minio:9000\nS3_REGION=us-east-1\nS3_BUCKET=business-suite\nS3_ACCESS_KEY=minioadmin\nS3_SECRET_KEY=minioadmin\n\n# Stripe\nSTRIPE_API_KEY=\nSTRIPE_WEBHOOK_SECRET=\n\n# Sentry (optional)\nSENTRY_DSN=\n\n# Telemetry\nPROMETHEUS_METRICS_PORT=2112\n\n```\n\n---\n\n## Makefile (selected targets)\n\n```makefile\n.PHONY: build run lint fmt migrate up down test docker-build docker-up\n\nbuild:\n\tgo build -o bin/api ./cmd/api\n\nrun: build\n\t./bin/api\n\nfmt:\n\tgofmt -s -w .\n\nlint:\n\tgolangci-lint run\n\nmigrate-up:\n\tmigrate -path=./migrations -database \"${DATABASE_URL}\" up\n\nmigrate-down:\n\tmigrate -path=./migrations -database \"${DATABASE_URL}\" down\n\ndocker-build:\n\tdocker build -t business-suite:local .\n\ndocker-up:\n\tdocker-compose up --build\n\ntest:\n\tgo test ./... -v\n```\n\n---\n\n## docker-compose.yml (dev ready)\n\n```yaml\nversion: '3.8'\nservices:\n  db:\n    image: postgres:15\n    environment:\n      - POSTGRES_USER=postgres\n      - POSTGRES_PASSWORD=postgres\n      - POSTGRES_DB=business_suite\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - db-data:/var/lib/postgresql/data\n\n  redis:\n    image: redis:7\n    ports:\n      - \"6379:6379\"\n\n  minio:\n    image: minio/minio\n    command: server /data\n    environment:\n      MINIO_ROOT_USER: minioadmin\n      MINIO_ROOT_PASSWORD: minioadmin\n    ports:\n      - \"9000:9000\"\n    volumes:\n      - minio-data:/data\n\n  api:\n    build: .\n    env_file: .env.example\n    depends_on:\n      - db\n      - redis\n      - minio\n    ports:\n      - \"8080:8080\"\n    command: [\"/bin/api\"]\n\nvolumes:\n  db-data:\n  minio-data:\n```\n\n---\n\n## Dockerfile\n\n```dockerfile\nFROM golang:1.21-alpine AS builder\nWORKDIR /src\nCOPY go.mod go.sum ./\nRUN apk add --no-cache git\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /bin/api ./cmd/api\n\nFROM alpine:latest\nRUN apk add --no-cache ca-certificates\nCOPY --from=builder /bin/api /bin/api\nEXPOSE 8080\nCMD [\"/bin/api\"]\n```\n\n---\n\n## go.mod (example)\n\n```\nmodule github.com/wardvisual/im-business-suite\n\ngo 1.21\n\nrequire (\n\tgithub.com/gin-gonic/gin v1.10.0\n\tgorm.io/gorm v1.27.0\n\tgorm.io/driver/postgres v1.6.1\n\tgithub.com/golang-migrate/migrate/v4 v4.15.2\n\tgithub.com/golang-jwt/jwt/v5 v5.0.0\n\tgolang.org/x/crypto v0.12.0\n\tgithub.com/hibiken/asynq v0.26.0\n\tgithub.com/getsentry/sentry-go v0.17.0\n\tgithub.com/sirupsen/logrus v1.10.0\n\tgithub.com/prometheus/client_golang v1.14.0\n)\n```\n\n---\n\n## Key code files\n\nBelow are minimal but complete examples for core files. Place them in the structure above.\n\n### `cmd/api/main.go`\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/wardvisual/im-business-suite/internal/config\"\n\t\"github.com/wardvisual/im-business-suite/internal/server\"\n)\n\nfunc main() {\n\tcfg, err := config.Load()\n\tif err != nil {\n\t\tlog.Fatalf(\"failed loading config: %v\", err)\n\t}\n\n\tr := server.New(cfg)\n\taddr := cfg.AppHost + \":\" + cfg.AppPort\n\tlog.Printf(\"listening on %s\", addr)\n\tif err := http.ListenAndServe(addr, r); err != nil {\n\t\tlog.Fatalf(\"server failed: %v\", err)\n\t}\n}\n```\n\n---\n\n### `internal/config/config.go`\n\n```go\npackage config\n\nimport (\n\t\"os\"\n)\n\ntype Config struct {\n\tAppEnv  string\n\tAppPort string\n\tAppHost string\n\tJWTSecret string\n\tDatabaseURL string\n\tRedisURL    string\n}\n\nfunc Load() (*Config, error) {\n\tc := \u0026Config{\n\t\tAppEnv: os.Getenv(\"APP_ENV\"),\n\t\tAppPort: os.Getenv(\"APP_PORT\"),\n\t\tAppHost: os.Getenv(\"APP_HOST\"),\n\t\tJWTSecret: os.Getenv(\"JWT_SECRET\"),\n\t\tDatabaseURL: os.Getenv(\"DATABASE_URL\"),\n\t\tRedisURL: os.Getenv(\"REDIS_URL\"),\n\t}\n\t// TODO: validate required fields and return error if missing\n\treturn c, nil\n}\n```\n\n---\n\n### `internal/server/server.go`\n\n```go\npackage server\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/wardvisual/im-business-suite/internal/config\"\n\t\"github.com/wardvisual/im-business-suite/internal/middleware\"\n\t\"github.com/wardvisual/im-business-suite/internal/modules/crm\"\n)\n\nfunc New(cfg *config.Config) *gin.Engine {\n\tr := gin.New()\n\tr.Use(gin.Recovery())\n\tr.Use(gin.Logger())\n\n\t// global middlewares\n\tr.Use(middleware.CORSMiddleware())\n\tr.Use(middleware.TenantMiddleware()) // sets tenant context\n\n\tapi := r.Group(\"/api\")\n\tv1 := api.Group(\"/v1\")\n\n\t// auth routes\n\t// TODO: mount auth handlers\n\n\t// sample CRM module\n\tcrm.RegisterRoutes(v1)\n\n\treturn r\n}\n```\n\n---\n\n### `internal/middleware/tenant.go`\n\n```go\npackage middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// TenantMiddleware extracts tenant info from JWT or header and stores it in context.\nfunc TenantMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// Example: prefer header X-Tenant-ID otherwise from JWT claim\n\t\ttenant := c.GetHeader(\"X-Tenant-ID\")\n\t\tif tenant == \"\" {\n\t\t\t// TODO: parse JWT and extract organization_id claim\n\t\t}\n\t\tif tenant == \"\" {\n\t\t\t// no tenant found — reject or set to public tenant depending on your policy\n\t\t\tc.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{\"error\": \"tenant required\"})\n\t\t\treturn\n\t\t}\n\t\tc.Set(\"tenant_id\", tenant)\n\t\tc.Next()\n\t}\n}\n```\n\n---\n\n### `internal/modules/crm/handler.go` (sample)\n\n```go\npackage crm\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc RegisterRoutes(rg *gin.RouterGroup) {\n\tr := rg.Group(\"/crm\")\n\tr.GET(\"/contacts\", listContacts)\n\tr.POST(\"/contacts\", createContact)\n}\n\nfunc listContacts(c *gin.Context) {\n\ttenant, _ := c.Get(\"tenant_id\")\n\t// fetch contacts filtered by tenant\n\tc.JSON(http.StatusOK, gin.H{\"tenant\": tenant, \"contacts\": []string{}})\n}\n\nfunc createContact(c *gin.Context) {\n\t// parse, validate, save with tenant id\n\tc.JSON(http.StatusCreated, gin.H{\"status\": \"ok\"})\n}\n```\n\n---\n\n### `internal/auth/jwt.go` (simple helpers)\n\n```go\npackage auth\n\nimport (\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\nfunc CreateAccessToken(secret string, subject string, tenantID string, minutes int) (string, error) {\n\tclaims := jwt.MapClaims{}\n\tclaims[\"sub\"] = subject\n\tclaims[\"org\"] = tenantID\n\tclaims[\"exp\"] = time.Now().Add(time.Minute * time.Duration(minutes)).Unix()\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\treturn token.SignedString([]byte(secret))\n}\n\nfunc ParseToken(secret string, tokenString string) (*jwt.Token, error) {\n\treturn jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {\n\t\treturn []byte(secret), nil\n\t})\n}\n```\n\n---\n\n## Database migration example (migrations/0001_init.up.sql)\n\n```sql\nCREATE TABLE organizations (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    name TEXT NOT NULL,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT now()\n);\n\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    email TEXT UNIQUE NOT NULL,\n    password_hash TEXT NOT NULL,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT now()\n);\n\nCREATE TABLE user_organizations (\n    user_id UUID REFERENCES users(id) ON DELETE CASCADE,\n    org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,\n    role TEXT NOT NULL,\n    PRIMARY KEY(user_id, org_id)\n);\n\nCREATE TABLE contacts (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,\n    name TEXT NOT NULL,\n    email TEXT,\n    phone TEXT,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT now()\n);\n```\n\nAnd the corresponding `down.sql` should drop these tables in reverse order.\n\n---\n\n## CI: .github/workflows/ci.yml (basic)\n\n```yaml\nname: CI\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: '1.21'\n      - name: Install deps\n        run: go mod download\n      - name: Run linters\n        run: |\n          go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest\n          golangci-lint run\n      - name: Run tests\n        run: go test ./... -v\n      - name: Build\n        run: go build -o bin/api ./cmd/api\n```\n\n---\n\n## Observability quick hooks\n\n* Expose `/metrics` for Prometheus using `promhttp.Handler()` in telemetry.\n* Capture panics and forward to Sentry with request context.\n* Log structured logs via `logrus` (or zap) — keep logs JSON for production.\n\n---\n\n## Notes on multi-tenancy \u0026 data isolation strategies\n\n* **Row-level tenancy** (recommended for small-medium scale): add `org_id` column to business tables and enforce filter in queries and DB indices. Simpler to start.\n* **Schema-per-tenant** (higher isolation): create a separate schema per tenant (Postgres) — more complex for migrations.\n* **DB-per-tenant** (highest isolation): separate DB instance per tenant — heavy management.\n\nStart with row-level tenancy; audit access at middleware to ensure no cross-tenant leakage.\n\n---\n\n## Security checklist for launch (minimum)\n\n* HTTPS only (TLS certs in production)\n* JWT secret from secure store (Vault or env var in CI secret)\n* Passwords hashed with bcrypt and strong pepper/salt policy\n* Input validation and prepared statements / ORM usage to avoid SQL injection\n* Rate limiting (IP-based and user-based)\n* File upload virus scanning or type checks (avoid storing executable content)\n* Audit logs for admin actions\n* Backups \u0026 automated backup tests\n\n---\n\n## Next steps \u0026 expansion ideas\n\n1. Add `modules/invoicing` with Stripe integration and webhook handling. Save invoices with `org_id`.\n2. Add `modules/analytics` that ingests events using `asynq` workers and stores time-series metrics.\n3. Add an internal API gateway or GraphQL layer for aggregated data across modules.\n4. Add white-labeling: per-organization branding config, custom domain routing (CNAME + tenant detection by hostname).\n\n---\n\n## README.md (for your repo root)\n\nBelow is a friendly README you can paste into `README.md` in the GitHub repo. It includes steps so Copilot can read and act on it.\n\n---\n\n# Business Suite — Boilerplate (README)\n\n**Quick start**\n\n1. Copy repository to your GitHub (or `git clone` this starter).\n2. Create `.env` from `.env.example` and set secrets (e.g., JWT_SECRET, DATABASE_URL).\n3. `make docker-up` to bring up Postgres, Redis, MinIO and the API (dev).\n4. Run DB migrations: `make migrate-up` (ensure `DATABASE_URL` in `.env` points to your dev DB).\n5. Start local server for development: `make run` or `go run ./cmd/api`.\n\n**Development notes**\n\n* Use `POST /api/v1/auth/register` to create first user (seed function can create first org \u0026 owner).\n* Use header `X-Tenant-ID` for the tenant in development if your token doesn't carry org claim.\n* Use `curl` or Postman for quick API exploration.\n\n**Testing**\n\n* Unit tests: `go test ./... -v`\n* Integration tests: add docker-compose override and use TestMain to run migrations and seed test data.\n\n**PR / Branching**\n\n* `main` for production-ready code.\n* `develop` for active dev.\n* Feature branches: `feature/\u003cshort-description\u003e`.\n\n**Production checklist (before launch)**\n\n* Move secrets to Vault or cloud secret manager.\n* Set up TLS (Let’s Encrypt / cloud load balancer).\n* Backups \u0026 restore drills for Postgres \u0026 object store.\n* Configure autoscaling and health checks (k8s HPA / cloud autoscale).\n* Legal/compliance: data residency, privacy policy, and TOS.\n\n**Roadmap**\n\n* MVP: Auth + CRM Lite (Contacts \u0026 Leads) + Invoicing basic.\n* v1: Billing + Dashboard + Team \u0026 Permissions.\n* v2: App marketplace (webhooks + OAuth for third-party integration).\n\n**Contact \u0026 Contributing**\n\n* Add issues, feature requests, or PRs.\n* Use GitHub Actions to run linters and tests on PRs.\n\n---\n\n## Final notes\n\nThis boilerplate is intentionally opinionated to get you shipping quickly while leaving room for production concerns. Copy the files above into your repository structure and iterate — start with `auth` + `tenant` middleware + the `crm` module to validate the concept with 1–2 customers.\n\nIf you want, I can now:\n\n* Generate a full ZIP of all these files (one-shot skeleton) with full code for each file.\n* Or produce full, fleshed-out implementations for specific packages (auth, db, middleware) one at a time.\n\nTell me which one you want next: `zip skeleton`, `full auth implementation`, or `db + migrations scripts`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwardvisual%2Fim-business-suite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwardvisual%2Fim-business-suite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwardvisual%2Fim-business-suite/lists"}