Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,56 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/tenant/models"
)
// Create creates a new tenant
// Uses batched writes to maintain consistency across denormalized tables
func (r *repository) Create(ctx context.Context, t *domaintenant.Tenant) error {
// Convert to table models
tenantByID := models.FromTenant(t)
tenantBySlug := models.FromTenantBySlug(t)
tenantByStatus := models.FromTenantByStatus(t)
// Create batch for atomic write
batch := r.session.NewBatch(gocql.LoggedBatch)
// Insert into tenants_by_id table
batch.Query(`INSERT INTO tenants_by_id (id, name, slug, status, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
tenantByID.ID, tenantByID.Name, tenantByID.Slug, tenantByID.Status,
tenantByID.CreatedAt, tenantByID.UpdatedAt,
tenantByID.CreatedFromIPAddress, tenantByID.CreatedFromIPTimestamp,
tenantByID.ModifiedFromIPAddress, tenantByID.ModifiedFromIPTimestamp)
// Insert into tenants_by_slug table
batch.Query(`INSERT INTO tenants_by_slug (slug, id, name, status, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
tenantBySlug.Slug, tenantBySlug.ID, tenantBySlug.Name, tenantBySlug.Status,
tenantBySlug.CreatedAt, tenantBySlug.UpdatedAt,
tenantBySlug.CreatedFromIPAddress, tenantBySlug.CreatedFromIPTimestamp,
tenantBySlug.ModifiedFromIPAddress, tenantBySlug.ModifiedFromIPTimestamp)
// Insert into tenants_by_status table
batch.Query(`INSERT INTO tenants_by_status (status, id, name, slug, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
tenantByStatus.Status, tenantByStatus.ID, tenantByStatus.Name, tenantByStatus.Slug,
tenantByStatus.CreatedAt, tenantByStatus.UpdatedAt,
tenantByStatus.CreatedFromIPAddress, tenantByStatus.CreatedFromIPTimestamp,
tenantByStatus.ModifiedFromIPAddress, tenantByStatus.ModifiedFromIPTimestamp)
// Execute batch
if err := r.session.ExecuteBatch(batch); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,43 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
"go.uber.org/zap"
)
// Delete deletes a tenant from all tables
// Uses batched writes to maintain consistency across denormalized tables
// Note: Consider implementing soft delete (status = 'deleted') instead
func (r *repository) Delete(ctx context.Context, id string) error {
// First, get the tenant to retrieve the slug and status
// (needed to delete from tenants_by_slug and tenants_by_status tables)
tenant, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Create batch for atomic delete
batch := r.session.NewBatch(gocql.LoggedBatch)
// Delete from tenants_by_id table
batch.Query(`DELETE FROM tenants_by_id WHERE id = ?`, id)
// Delete from tenants_by_slug table
batch.Query(`DELETE FROM tenants_by_slug WHERE slug = ?`, tenant.Slug)
// Delete from tenants_by_status table
batch.Query(`DELETE FROM tenants_by_status WHERE status = ? AND id = ?`,
string(tenant.Status), id)
// Execute batch
if err := r.session.ExecuteBatch(batch); err != nil {
r.logger.Error("failed to delete tenant",
zap.String("tenant_id", id),
zap.Error(err))
return err
}
return nil
}

View file

@ -0,0 +1,62 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/tenant/models"
)
// GetByID retrieves a tenant by ID
func (r *repository) GetByID(ctx context.Context, id string) (*domaintenant.Tenant, error) {
var tenantByID models.TenantByID
query := `SELECT id, name, slug, status, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp
FROM tenants_by_id
WHERE id = ?`
err := r.session.Query(query, id).
Consistency(gocql.Quorum).
Scan(&tenantByID.ID, &tenantByID.Name, &tenantByID.Slug, &tenantByID.Status,
&tenantByID.CreatedAt, &tenantByID.UpdatedAt,
&tenantByID.CreatedFromIPAddress, &tenantByID.CreatedFromIPTimestamp,
&tenantByID.ModifiedFromIPAddress, &tenantByID.ModifiedFromIPTimestamp)
if err != nil {
if err == gocql.ErrNotFound {
return nil, domaintenant.ErrTenantNotFound
}
return nil, err
}
return tenantByID.ToTenant(), nil
}
// GetBySlug retrieves a tenant by slug
func (r *repository) GetBySlug(ctx context.Context, slug string) (*domaintenant.Tenant, error) {
var tenantBySlug models.TenantBySlug
query := `SELECT slug, id, name, status, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp
FROM tenants_by_slug
WHERE slug = ?`
err := r.session.Query(query, slug).
Consistency(gocql.Quorum).
Scan(&tenantBySlug.Slug, &tenantBySlug.ID, &tenantBySlug.Name, &tenantBySlug.Status,
&tenantBySlug.CreatedAt, &tenantBySlug.UpdatedAt,
&tenantBySlug.CreatedFromIPAddress, &tenantBySlug.CreatedFromIPTimestamp,
&tenantBySlug.ModifiedFromIPAddress, &tenantBySlug.ModifiedFromIPTimestamp)
if err != nil {
if err == gocql.ErrNotFound {
return nil, domaintenant.ErrTenantNotFound
}
return nil, err
}
return tenantBySlug.ToTenant(), nil
}

View file

@ -0,0 +1,21 @@
package tenant
import (
"github.com/gocql/gocql"
"go.uber.org/zap"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
type repository struct {
session *gocql.Session
logger *zap.Logger
}
// ProvideRepository creates a new tenant repository
func ProvideRepository(session *gocql.Session, logger *zap.Logger) domaintenant.Repository {
return &repository{
session: session,
logger: logger,
}
}

View file

@ -0,0 +1,37 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/tenant/models"
)
// List retrieves all tenants (paginated)
// Note: This is a table scan and should be used sparingly in production
// Consider adding a tenants_by_status table for filtered queries
func (r *repository) List(ctx context.Context, limit int) ([]*domaintenant.Tenant, error) {
query := `SELECT id, name, slug, status, created_at, updated_at
FROM tenants_by_id
LIMIT ?`
iter := r.session.Query(query, limit).
Consistency(gocql.Quorum).
Iter()
var tenants []*domaintenant.Tenant
var tenantByID models.TenantByID
for iter.Scan(&tenantByID.ID, &tenantByID.Name, &tenantByID.Slug, &tenantByID.Status,
&tenantByID.CreatedAt, &tenantByID.UpdatedAt) {
tenants = append(tenants, tenantByID.ToTenant())
}
if err := iter.Close(); err != nil {
return nil, err
}
return tenants, nil
}

View file

@ -0,0 +1,40 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/tenant/models"
)
// ListByStatus retrieves all tenants with the specified status (paginated)
// Uses the tenants_by_status table for efficient filtering
func (r *repository) ListByStatus(ctx context.Context, status domaintenant.Status, limit int) ([]*domaintenant.Tenant, error) {
query := `SELECT status, id, name, slug, created_at, updated_at,
created_from_ip_address, created_from_ip_timestamp, modified_from_ip_address, modified_from_ip_timestamp
FROM tenants_by_status
WHERE status = ?
LIMIT ?`
iter := r.session.Query(query, string(status), limit).
Consistency(gocql.Quorum).
Iter()
var tenants []*domaintenant.Tenant
var tenantByStatus models.TenantByStatus
for iter.Scan(&tenantByStatus.Status, &tenantByStatus.ID, &tenantByStatus.Name, &tenantByStatus.Slug,
&tenantByStatus.CreatedAt, &tenantByStatus.UpdatedAt,
&tenantByStatus.CreatedFromIPAddress, &tenantByStatus.CreatedFromIPTimestamp,
&tenantByStatus.ModifiedFromIPAddress, &tenantByStatus.ModifiedFromIPTimestamp) {
tenants = append(tenants, tenantByStatus.ToTenant())
}
if err := iter.Close(); err != nil {
return nil, err
}
return tenants, nil
}

View file

@ -0,0 +1,61 @@
package models
import (
"time"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// TenantByID represents the tenants_by_id table
// Query pattern: Get tenant by ID
// Primary key: id
type TenantByID struct {
ID string `db:"id"`
Name string `db:"name"`
Slug string `db:"slug"`
Status string `db:"status"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
// CWE-359: IP address tracking for GDPR compliance
CreatedFromIPAddress string `db:"created_from_ip_address"`
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"`
ModifiedFromIPAddress string `db:"modified_from_ip_address"`
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"`
}
// ToTenant converts table model to domain entity
func (t *TenantByID) ToTenant() *tenant.Tenant {
return &tenant.Tenant{
ID: t.ID,
Name: t.Name,
Slug: t.Slug,
Status: tenant.Status(t.Status),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}
// FromTenant converts domain entity to table model
func FromTenant(t *tenant.Tenant) *TenantByID {
return &TenantByID{
ID: t.ID,
Name: t.Name,
Slug: t.Slug,
Status: string(t.Status),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}

View file

@ -0,0 +1,61 @@
package models
import (
"time"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// TenantBySlug represents the tenants_by_slug table
// Query pattern: Get tenant by slug (URL-friendly identifier)
// Primary key: slug
type TenantBySlug struct {
Slug string `db:"slug"`
ID string `db:"id"`
Name string `db:"name"`
Status string `db:"status"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
// CWE-359: IP address tracking for GDPR compliance
CreatedFromIPAddress string `db:"created_from_ip_address"`
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"`
ModifiedFromIPAddress string `db:"modified_from_ip_address"`
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"`
}
// ToTenant converts table model to domain entity
func (t *TenantBySlug) ToTenant() *tenant.Tenant {
return &tenant.Tenant{
ID: t.ID,
Name: t.Name,
Slug: t.Slug,
Status: tenant.Status(t.Status),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}
// FromTenantBySlug converts domain entity to table model
func FromTenantBySlug(t *tenant.Tenant) *TenantBySlug {
return &TenantBySlug{
Slug: t.Slug,
ID: t.ID,
Name: t.Name,
Status: string(t.Status),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}

View file

@ -0,0 +1,61 @@
package models
import (
"time"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// TenantByStatus represents the tenants_by_status table
// Query pattern: List tenants by status (e.g., active, inactive, suspended)
// Primary key: (status, id) - status is partition key, id is clustering key
type TenantByStatus struct {
Status string `db:"status"`
ID string `db:"id"`
Name string `db:"name"`
Slug string `db:"slug"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
// CWE-359: IP address tracking for GDPR compliance
CreatedFromIPAddress string `db:"created_from_ip_address"`
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"`
ModifiedFromIPAddress string `db:"modified_from_ip_address"`
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"`
}
// ToTenant converts table model to domain entity
func (t *TenantByStatus) ToTenant() *tenant.Tenant {
return &tenant.Tenant{
ID: t.ID,
Name: t.Name,
Slug: t.Slug,
Status: tenant.Status(t.Status),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}
// FromTenantByStatus converts domain entity to table model
func FromTenantByStatus(t *tenant.Tenant) *TenantByStatus {
return &TenantByStatus{
Status: string(t.Status),
ID: t.ID,
Name: t.Name,
Slug: t.Slug,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
// CWE-359: IP address tracking
CreatedFromIPAddress: t.CreatedFromIPAddress,
CreatedFromIPTimestamp: t.CreatedFromIPTimestamp,
ModifiedFromIPAddress: t.ModifiedFromIPAddress,
ModifiedFromIPTimestamp: t.ModifiedFromIPTimestamp,
}
}

View file

@ -0,0 +1,68 @@
package tenant
import (
"context"
"github.com/gocql/gocql"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/tenant/models"
)
// Update updates an existing tenant
// Uses batched writes to maintain consistency across denormalized tables
func (r *repository) Update(ctx context.Context, t *domaintenant.Tenant) error {
// Get the old tenant to check if status changed
oldTenant, err := r.GetByID(ctx, t.ID)
if err != nil {
return err
}
// Convert to table models
tenantByID := models.FromTenant(t)
tenantBySlug := models.FromTenantBySlug(t)
tenantByStatus := models.FromTenantByStatus(t)
// Create batch for atomic write
batch := r.session.NewBatch(gocql.LoggedBatch)
// Update tenants_by_id table
batch.Query(`UPDATE tenants_by_id SET name = ?, slug = ?, status = ?, updated_at = ?
WHERE id = ?`,
tenantByID.Name, tenantByID.Slug, tenantByID.Status, tenantByID.UpdatedAt,
tenantByID.ID)
// Update tenants_by_slug table
// Note: If slug changed, we need to delete old slug entry and insert new one
// For simplicity, we'll update in place (slug changes require delete + create)
batch.Query(`UPDATE tenants_by_slug SET id = ?, name = ?, status = ?, updated_at = ?
WHERE slug = ?`,
tenantBySlug.ID, tenantBySlug.Name, tenantBySlug.Status, tenantBySlug.UpdatedAt,
tenantBySlug.Slug)
// Handle tenants_by_status table
// If status changed, delete from old partition and insert into new one
if oldTenant.Status != t.Status {
// Delete from old status partition
batch.Query(`DELETE FROM tenants_by_status WHERE status = ? AND id = ?`,
string(oldTenant.Status), t.ID)
// Insert into new status partition
batch.Query(`INSERT INTO tenants_by_status (status, id, name, slug, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)`,
tenantByStatus.Status, tenantByStatus.ID, tenantByStatus.Name, tenantByStatus.Slug,
tenantByStatus.CreatedAt, tenantByStatus.UpdatedAt)
} else {
// Status didn't change, just update in place
batch.Query(`UPDATE tenants_by_status SET name = ?, slug = ?, updated_at = ?
WHERE status = ? AND id = ?`,
tenantByStatus.Name, tenantByStatus.Slug, tenantByStatus.UpdatedAt,
tenantByStatus.Status, tenantByStatus.ID)
}
// Execute batch
if err := r.session.ExecuteBatch(batch); err != nil {
return err
}
return nil
}