Skip to main content

Task 03 — Industries with Their Users

Difficulty: Beginner–Intermediate
Goal: Add an admin endpoint that returns all industries, each with the list of users belonging to that industry, merged into a single response.


What you'll learn
  • How to make multiple parallel DB calls from a single service
  • How to join/merge two datasets in the service layer
  • Using ThreadPoolExecutor for concurrent queries

Endpoint spec

GET /api/admin/industries/with-users
Authorization: Bearer <admin-token>

Response 200:
{
"status": "success",
"status_code": 200,
"data": [
{
"industryId": "I001",
"industryName": "Industry A",
"users": [
{ "userId": "I001_1234_alice", "username": "alice", "type": "DEFAULT", "active": true },
{ "userId": "I001_5678_bob", "username": "bob", "type": "OTP", "active": true }
]
}
]
}
Real field names from the backend
  • Industry documents use industryId (format: I1, I2, I100) and industryName
  • User documents use userId, username, type (DEFAULT/OTP/SSO/microsoft), active, email, phoneNo
  • There is no name field on users — the display name field is username

Files to create / edit

FileWhat to do
app/routes/admin/industry_users.pyDefine namespace + resource
app/services/admin/industry_users_service.pyFetch industries + users, merge
app/formatters/admin/industry_users_formatter.pyShape the merged output
app/apis/admin.pyRegister the namespace
app/database/queires_list.pyNo new queries needed — use existing methods
app/database/database_supporter.pyNo new methods needed — use get_workspace_data() and get_all_users_of_industry()

Service — key logic

Use ThreadPoolExecutor to fetch users for all industries concurrently instead of making N sequential calls.

The real DB method to get all users for an industry is DatabaseSupporter.get_all_users_of_industry(industry_id) — it returns id, username, type, active fields.

To get workspace data (all industries), use DatabaseSupporter.get_workspace_data() — it already exists and returns industryId, industryName, meta.logo, meta.sector, meta.subSector.

# app/services/admin/industry_users_service.py
from concurrent.futures import ThreadPoolExecutor
from app.database.database_supporter import DatabaseSupporter

class IndustryUsersService:
def get_all(self):
# get_workspace_data() already exists — returns all industries with key fields
industries = DatabaseSupporter.get_workspace_data()

def fetch_users(industry):
# get_all_users_of_industry() already exists — returns id, username, type, active
users = DatabaseSupporter.get_all_users_of_industry(industry['industryId'])
return {**industry, 'users': users}

with ThreadPoolExecutor() as executor:
results = list(executor.map(fetch_users, industries))

return results

Formatter

class IndustryUsersFormatter:
def __init__(self, data):
self.data = data

def format(self):
return [
{
'industryId': ind['industryId'],
'industryName': ind.get('industryName'), # real field is 'industryName', not 'name'
'users': [
{
'userId': u.get('id'), # get_all_users_of_industry returns 'id'
'username': u.get('username'),
'type': u.get('type'), # 'DEFAULT', 'OTP', 'SSO', 'microsoft'
'active': u.get('active'),
}
for u in ind.get('users', [])
]
}
for ind in self.data
]

Things to watch out for
  • Industries with zero users should still appear in the response, with "users": [].
  • Keep the user query scoped to one industry at a time — never SELECT * FROM c across the full users container.

Done when
  • Response contains all industries
  • Each industry object has a users array (empty array is fine if no users)
  • Response time stays reasonable even with many industries (parallel fetching works)