← Back to Modules

dbbasic-groups Specification

Version: 1.0 Status: Specification Author: DBBasic Project Date: November 2025

Links: - PyPI: https://pypi.org/project/dbbasic-groups/ (not yet published) - GitHub: https://github.com/askrobots/dbbasic-groups (not yet published) - Specification: http://dbbasic.com/groups-spec


Philosophy

"Groups are just shared context with access control."

The critical insight: Most group systems over-complicate what is fundamentally simple: a named container that some users can access and some can't. Everything else (posts, discussions, files) belongs to other modules.

Design Principles

  1. Groups = Access Control: Groups define who can access what
  2. Membership Management: Join/leave, approve/reject
  3. Role-Based: Owner, admin, member roles
  4. Integration-Ready: Works with forum, jobs, follows
  5. No Duplication: Don't reinvent posts/discussions (use dbbasic-forum)

The Problem with Existing Group Systems

Too Complex: Facebook Groups

- Posts, comments, reactions, polls, events, files, photos
- Albums, live videos, shops, fundraisers, badges, units
- Member questions, group rules, learning units, member bio
- 50+ different features

Result: Feature bloat, confusing UX, slow performance.

Too Simple: Email Lists

- Just a list of emails
- No web interface
- No roles or permissions

Result: No visibility, no moderation tools, spam problems.

The Sweet Spot: Access Control + Integration

Group = {
    name: "Django Developers",
    description: "...",
    members: [u001, u002, u003],
    privacy: "public"
}

# Then integrate:
- Forum posts scoped to group
- Jobs posted to group
- Files shared with group
- Events for group members

Why this works: - Single responsibility (access control) - Composable (works with other modules) - Simple data model - No feature duplication


TSV Storage Schema

groups.tsv

id  name    slug    description privacy created_by  created_at  updated_at  member_count

Fields: - id: Unique group ID (e.g., "g001", "g002") - name: Group name (e.g., "Django Developers") - slug: URL-friendly name (e.g., "django-developers") - description: Group description (markdown supported) - privacy: Group privacy (public, private, secret) - created_by: User who created the group - created_at: When group was created - updated_at: When group was last modified - member_count: Cached count of members

Example Data

id  name    slug    description privacy created_by  created_at  updated_at  member_count
g001    Django Developers   django-developers   Community for Django web developers public  u042    2025-10-01T10:00:00 2025-11-01T15:00:00 342
g002    Python Freelancers  python-freelancers  Freelance Python developers private u055    2025-10-05T14:00:00 2025-11-01T10:00:00 128
g003    Client Network  client-network  Vetted clients only secret  u088    2025-10-10T09:00:00 2025-10-15T12:00:00 45

memberships.tsv

id  group_id    user_id role    status  joined_at   updated_at

Fields: - id: Unique membership ID (e.g., "m001", "m002") - group_id: Group being joined - user_id: User joining - role: Member role (owner, admin, member) - status: Membership status (active, pending, banned, left) - joined_at: When user joined - updated_at: When membership was last modified

Example Data

id  group_id    user_id role    status  joined_at   updated_at
m001    g001    u042    owner   active  2025-10-01T10:00:00 2025-10-01T10:00:00
m002    g001    u055    admin   active  2025-10-01T11:00:00 2025-10-01T11:00:00
m003    g001    u077    member  active  2025-10-02T09:00:00 2025-10-02T09:00:00
m004    g001    u099    member  pending 2025-11-01T14:00:00 2025-11-01T14:00:00
m005    g002    u055    owner   active  2025-10-05T14:00:00 2025-10-05T14:00:00
m006    g002    u077    member  banned  2025-10-10T10:00:00 2025-10-15T16:00:00

Core API

Python API

from dbbasic_groups import Groups

# Initialize
groups = Groups('/path/to/data')

# Create group
group_id = groups.create(
    name='Django Developers',
    slug='django-developers',
    description='Community for Django web developers',
    privacy='public',
    created_by='u042'
)

# Get group
group = groups.get('g001')

# Get group by slug
group = groups.get_by_slug('django-developers')

# List groups
all_groups = groups.list()
public_groups = groups.list(privacy='public')
my_groups = groups.list_by_user('u042')

# Update group
groups.update('g001', description='Updated description')

# Delete group
groups.delete('g001')

# Join group
groups.join(group_id='g001', user_id='u077')

# Leave group
groups.leave(group_id='g001', user_id='u077')

# Approve membership (for private groups)
groups.approve_membership(membership_id='m004')

# Reject membership
groups.reject_membership(membership_id='m004')

# Ban member
groups.ban_member(group_id='g001', user_id='u077')

# Get members
members = groups.get_members('g001')

# Get member role
role = groups.get_role(group_id='g001', user_id='u042')

# Check membership
is_member = groups.is_member(group_id='g001', user_id='u077')

# Promote to admin
groups.promote(group_id='g001', user_id='u055', role='admin')

# Demote to member
groups.demote(group_id='g001', user_id='u055', role='member')

Group Privacy Levels

Public

groups.create(..., privacy='public')

Characteristics: - Anyone can see group - Anyone can see members - Anyone can see content - Anyone can join (no approval needed)

Use cases: Open communities, public discussions

Private

groups.create(..., privacy='private')

Characteristics: - Anyone can see group exists - Only members can see member list - Only members can see content - Join requires approval

Use cases: Professional networks, curated communities

Secret

groups.create(..., privacy='secret')

Characteristics: - Only members can see group exists - Only members can see member list - Only members can see content - Join by invitation only

Use cases: Private clients, inner circles


Member Roles

Owner

Admin

Member


Membership Status Flow

         ┌──────────┐
         │ PENDING  │ (for private groups)
         └────┬─────┘
              │
              ├─────→ ACTIVE (approved by admin)
              │
              └─────→ REJECTED (declined by admin)

┌──────────┐
│  ACTIVE  │ (member is in group)
└────┬─────┘
     │
     ├─────→ LEFT (member left voluntarily)
     │
     └─────→ BANNED (removed by admin/owner)

Web Integration

With dbbasic-web

# api/groups/create.py
from dbbasic_groups import Groups
from dbbasic_web.responses import json_response

def handle(request):
    groups = Groups('/data')

    group_id = groups.create(
        name=request.form['name'],
        slug=request.form['slug'],
        description=request.form['description'],
        privacy=request.form['privacy'],
        created_by=request.user['id']
    )

    return json_response({'group_id': group_id})

# api/groups/join.py
def handle(request):
    groups = Groups('/data')

    groups.join(
        group_id=request.form['group_id'],
        user_id=request.user['id']
    )

    return json_response({'success': True})

# api/groups/[slug].py
def handle(request, slug):
    groups = Groups('/data')

    group = groups.get_by_slug(slug)

    # Check access
    if group['privacy'] == 'secret':
        if not groups.is_member(group['id'], request.user['id']):
            return json_response({'error': 'Not found'}, status=404)

    members = groups.get_members(group['id'])

    return json_response({
        'group': group,
        'members': members
    })

Display Components

<!-- Group card -->
<div class="group-card">
    <h3>{{ group.name }}</h3>
    <div class="group-meta">
        <span class="privacy">{{ group.privacy }}</span>
        <span class="members">{{ group.member_count }} members</span>
    </div>
    <p>{{ group.description | truncate(200) }}</p>
    <a href="/groups/{{ group.slug }}" class="btn">View Group</a>
</div>

<!-- Join button -->
{% if not is_member %}
<form method="POST" action="/api/groups/join">
    <input type="hidden" name="group_id" value="{{ group.id }}">
    <button type="submit">
        {% if group.privacy == 'private' %}
            Request to Join
        {% else %}
            Join Group
        {% endif %}
    </button>
</form>
{% endif %}

CLI Commands

List groups

# All groups
dbbasic groups list

# By privacy
dbbasic groups list --privacy public

# Groups for user
dbbasic groups list --user u042

Create group

dbbasic groups create \
    --name "Django Developers" \
    --slug django-developers \
    --description "Community for Django developers" \
    --privacy public \
    --creator u042

View members

dbbasic groups members g001

Join group

dbbasic groups join g001 --user u077

Ban member

dbbasic groups ban g001 --user u077

Admin Interface

Auto-discovered by dbbasic-admin:

# Automatic CRUD interface at /admin/groups/
# Shows:
# - Recent groups
# - Filter by privacy
# - Member count analytics
# - Pending approvals
# - Ban management

Integration with Other Modules

With dbbasic-forum

Scope forum threads to groups:

from dbbasic_groups import Groups
from dbbasic_forum import Forum

groups = Groups('/data')
forum = Forum('/data')

# Create group-specific forum thread
thread_id = forum.create_thread(
    title='Django 5.0 Discussion',
    content='...',
    author_id='u042',
    group_id='g001'  # Only group members can see
)

# Get threads for group
group_threads = forum.get_threads(group_id='g001')

With dbbasic-jobs

Post jobs to specific groups:

from dbbasic_groups import Groups
from dbbasic_jobs import Jobs

groups = Groups('/data')
jobs = Jobs('/data')

# Create job for group
job_id = jobs.create(
    title='Django Developer Needed',
    description='...',
    client_id='u042',
    group_id='g001'  # Only group members see this job
)

# Get jobs for group
group_jobs = jobs.list(group_id='g001')

With dbbasic-follows

Auto-follow group members:

from dbbasic_groups import Groups
from dbbasic_follows import Follows

groups = Groups('/data')
follows = Follows('/data')

# When user joins group
groups.join(group_id='g001', user_id='u077')

# Suggest following other members
members = groups.get_members('g001')
for member in members:
    if member['user_id'] != 'u077':
        # Suggest following
        follows.suggest(from_user='u077', to_user=member['user_id'])

Search and Filtering

By Name

# Search groups by name
results = groups.search('Django')

By Privacy

# All public groups
public = groups.list(privacy='public')

By Member

# Groups user belongs to
my_groups = groups.list_by_user('u042')

# Groups user owns
owned = groups.list_by_user('u042', role='owner')

Advanced Features

1. Group Categories

Add categories for better organization:

groups.create(
    ...,
    category='technology'
)

# List by category
tech_groups = groups.list(category='technology')

2. Group Tags

Add tags for discovery:

groups.create(
    ...,
    tags=['python', 'django', 'web']
)

# Search by tag
python_groups = groups.search_by_tag('python')

3. Member Limits

Limit group size:

groups.create(
    ...,
    max_members=100
)

# Check if group is full
if group['member_count'] >= group['max_members']:
    raise ValueError('Group is full')

4. Invitation System

Invite users to private/secret groups:

# Send invitation
groups.invite(
    group_id='g001',
    from_user='u042',
    to_user='u077'
)

# Accept invitation
groups.accept_invitation(invitation_id='inv001')

Implementation Size

Target: ~250 lines of Python

dbbasic_groups/
├── __init__.py         # 10 lines
├── groups.py           # 120 lines (CRUD, search)
├── memberships.py      # 80 lines (join, leave, roles)
├── permissions.py      # 30 lines (access control)
└── cli.py             # 10 lines (command-line interface)

Total: ~250 lines

Security Considerations

1. Privacy Enforcement

Secret groups should be invisible to non-members:

def get_by_slug(self, slug, requesting_user_id=None):
    group = self.db.get_by_slug(slug)

    if group['privacy'] == 'secret':
        if not self.is_member(group['id'], requesting_user_id):
            raise NotFoundError('Group not found')

    return group

2. Permission Checks

Only owner/admin can manage group:

def ban_member(self, group_id, user_id, requesting_user_id):
    role = self.get_role(group_id, requesting_user_id)

    if role not in ['owner', 'admin']:
        raise PermissionError('Only owner/admin can ban members')

    # ...

3. Owner Protection

Owner cannot be removed:

def remove_member(self, group_id, user_id):
    membership = self.get_membership(group_id, user_id)

    if membership['role'] == 'owner':
        raise ValueError('Cannot remove owner')

    # ...

grep-able Data

# Find all public groups
grep "public" data/groups.tsv

# Find groups created by user u042
grep "u042" data/groups.tsv

# Find active memberships for group g001
grep "g001\t.*\tactive" data/memberships.tsv

# Find all admins
grep "admin" data/memberships.tsv

Why This Design?

1. Single Responsibility

Groups only handle access control. They don't: - Store posts (use dbbasic-forum) - Store files (use dbbasic-upload) - Store events (use dbbasic-events)

Why? Separation of concerns, composability, no duplication.

2. Privacy Levels Matter

Three levels (public, private, secret) cover all use cases:

Public: Open communities (Python user group)
Private: Professional networks (consulting collective)
Secret: Inner circles (private client group)

3. Simple Role Model

Only 3 roles: owner, admin, member

Most group systems have 10+ roles that confuse users: - "Moderator" - "Contributor" - "Viewer" - "Editor" - etc.

We skip all that. Either you can manage the group (owner/admin) or you can't (member).

4. Status-Based Membership

Four statuses cover the lifecycle: - pending: Waiting approval - active: Current member - left: Departed voluntarily - banned: Removed by admin

Clean state machine, no ambiguity.


Comparison to Existing Systems

Feature Facebook Groups LinkedIn Groups Slack dbbasic-groups
Lines of Code ~500K ~500K ~500K ~250
Privacy Levels 3 2 3 3
Member Roles 5+ 3 7+ 3
Posts/Discussions Built-in Built-in Built-in Use dbbasic-forum
File Storage Built-in Built-in Built-in Use dbbasic-upload
TSV Storage No No No Yes
grep-able No No No Yes

Example Use Cases

1. Freelancer Collective

# Create private group for vetted freelancers
group_id = groups.create(
    name='Python Freelancers',
    privacy='private',
    created_by='u042'
)

# Members apply to join
groups.join(group_id, 'u055')  # Status: pending

# Owner approves
groups.approve_membership(membership_id)

# Post jobs to group only
jobs.create(..., group_id=group_id)

2. Public Community

# Create public Django community
group_id = groups.create(
    name='Django Developers',
    privacy='public',
    created_by='u042'
)

# Anyone can join instantly
groups.join(group_id, 'u077')  # Status: active (no approval)

# Group-specific forum
forum.create_thread(..., group_id=group_id)

3. Private Client Network

# Create secret group for high-value clients
group_id = groups.create(
    name='Elite Clients',
    privacy='secret',
    created_by='u042'
)

# Only visible to members
# Invite-only access
groups.invite(group_id, from_user='u042', to_user='u055')

Status

Version: 1.0 Specification Implementation: Not yet started Expected Size: ~250 lines Dependencies: dbbasic-tsv only

Optional Integrations: - dbbasic-forum (group discussions) - dbbasic-jobs (group job board) - dbbasic-follows (member connections) - dbbasic-upload (group files)


Last Updated: November 2025