Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
2be59d51
Commit
2be59d51
authored
May 12, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: refine market admin controls and user filters
parent
b17195c8
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
701 additions
and
13 deletions
+701
-13
database.py
aisbf/database.py
+17
-1
admin.py
aisbf/routes/dashboard/admin.py
+1
-0
settings.py
aisbf/routes/dashboard/settings.py
+9
-3
base.html
templates/base.html
+2
-1
admin_payment_settings.html
templates/dashboard/admin_payment_settings.html
+44
-0
users.html
templates/dashboard/users.html
+64
-8
test_dashboard_user_market_controls.py
tests/routes/test_dashboard_user_market_controls.py
+328
-0
test_database_user_market_filters.py
tests/test_database_user_market_filters.py
+236
-0
No files found.
aisbf/database.py
View file @
2be59d51
...
@@ -1503,7 +1503,8 @@ class DatabaseManager:
...
@@ -1503,7 +1503,8 @@ class DatabaseManager:
def
get_users_paginated
(
self
,
page
:
int
=
1
,
limit
:
int
=
25
,
search
:
str
=
None
,
def
get_users_paginated
(
self
,
page
:
int
=
1
,
limit
:
int
=
25
,
search
:
str
=
None
,
order_by
:
str
=
'created_at'
,
direction
:
str
=
'desc'
,
order_by
:
str
=
'created_at'
,
direction
:
str
=
'desc'
,
status_filter
:
str
=
None
,
role_filter
:
str
=
None
)
->
Dict
:
status_filter
:
str
=
None
,
role_filter
:
str
=
None
,
tier_filter
:
str
=
None
,
market_export_filter
:
str
=
None
)
->
Dict
:
"""
"""
Get paginated users with search, sorting, and filtering support.
Get paginated users with search, sorting, and filtering support.
...
@@ -1515,6 +1516,8 @@ class DatabaseManager:
...
@@ -1515,6 +1516,8 @@ class DatabaseManager:
direction: Sort direction (asc or desc)
direction: Sort direction (asc or desc)
status_filter: Optional status filter ('active', 'inactive', or None)
status_filter: Optional status filter ('active', 'inactive', or None)
role_filter: Optional role filter ('admin', 'user', or None)
role_filter: Optional role filter ('admin', 'user', or None)
tier_filter: Optional account tier id filter
market_export_filter: Optional market export filter ('exporting', 'not_exporting', or None)
Returns:
Returns:
Dictionary with 'users' list and 'total' count
Dictionary with 'users' list and 'total' count
...
@@ -1575,6 +1578,19 @@ class DatabaseManager:
...
@@ -1575,6 +1578,19 @@ class DatabaseManager:
where_conditions
.
append
(
f
'u.role = {placeholder}'
)
where_conditions
.
append
(
f
'u.role = {placeholder}'
)
params
.
append
(
role_filter
)
params
.
append
(
role_filter
)
if
tier_filter
:
where_conditions
.
append
(
f
'u.tier_id = {placeholder}'
)
params
.
append
(
tier_filter
)
if
market_export_filter
==
'exporting'
:
where_conditions
.
append
(
f
'EXISTS (SELECT 1 FROM market_listings ml WHERE ml.owner_user_id = u.id AND ml.is_active = 1)'
)
elif
market_export_filter
==
'not_exporting'
:
where_conditions
.
append
(
f
'NOT EXISTS (SELECT 1 FROM market_listings ml WHERE ml.owner_user_id = u.id AND ml.is_active = 1)'
)
# Build final WHERE clause
# Build final WHERE clause
where_clause
=
'WHERE '
+
' AND '
.
join
(
where_conditions
)
if
where_conditions
else
''
where_clause
=
'WHERE '
+
' AND '
.
join
(
where_conditions
)
if
where_conditions
else
''
...
...
aisbf/routes/dashboard/admin.py
View file @
2be59d51
...
@@ -1510,6 +1510,7 @@ async def dashboard_admin_payment_settings(request: Request):
...
@@ -1510,6 +1510,7 @@ async def dashboard_admin_payment_settings(request: Request):
"session"
:
request
.
session
,
"session"
:
request
.
session
,
"currency_symbol"
:
DatabaseRegistry
.
get_config_database
()
.
get_currency_settings
()
.
get
(
'currency_symbol'
,
'$'
),
"currency_symbol"
:
DatabaseRegistry
.
get_config_database
()
.
get_currency_settings
()
.
get
(
'currency_symbol'
,
'$'
),
"market_settings"
:
DatabaseRegistry
.
get_config_database
()
.
get_market_settings
(),
"market_settings"
:
DatabaseRegistry
.
get_config_database
()
.
get_market_settings
(),
"market_listings"
:
DatabaseRegistry
.
get_config_database
()
.
list_market_listings
(
active_only
=
False
),
}
}
)
)
...
...
aisbf/routes/dashboard/settings.py
View file @
2be59d51
...
@@ -761,7 +761,9 @@ async def dashboard_users(
...
@@ -761,7 +761,9 @@ async def dashboard_users(
order_by
:
str
=
Query
(
'created_at'
,
pattern
=
'^(username|last_login|created_at|tier_name)$'
),
order_by
:
str
=
Query
(
'created_at'
,
pattern
=
'^(username|last_login|created_at|tier_name)$'
),
direction
:
str
=
Query
(
'desc'
,
pattern
=
'^(asc|desc)$'
),
direction
:
str
=
Query
(
'desc'
,
pattern
=
'^(asc|desc)$'
),
status_filter
:
str
=
Query
(
None
,
pattern
=
'^(active|inactive)$'
),
status_filter
:
str
=
Query
(
None
,
pattern
=
'^(active|inactive)$'
),
role_filter
:
str
=
Query
(
None
,
pattern
=
'^(admin|user)$'
)
role_filter
:
str
=
Query
(
None
,
pattern
=
'^(admin|user)$'
),
tier_filter
:
str
=
Query
(
None
),
market_export_filter
:
str
=
Query
(
None
,
pattern
=
'^(exporting|not_exporting)$'
)
):
):
"""Admin user management page"""
"""Admin user management page"""
auth_check
=
require_admin
(
request
)
auth_check
=
require_admin
(
request
)
...
@@ -778,7 +780,9 @@ async def dashboard_users(
...
@@ -778,7 +780,9 @@ async def dashboard_users(
order_by
=
order_by
,
order_by
=
order_by
,
direction
=
direction
,
direction
=
direction
,
status_filter
=
status_filter
,
status_filter
=
status_filter
,
role_filter
=
role_filter
role_filter
=
role_filter
,
tier_filter
=
tier_filter
,
market_export_filter
=
market_export_filter
,
)
)
users
=
result
[
'users'
]
users
=
result
[
'users'
]
...
@@ -816,7 +820,9 @@ async def dashboard_users(
...
@@ -816,7 +820,9 @@ async def dashboard_users(
"order_by"
:
order_by
,
"order_by"
:
order_by
,
"direction"
:
direction
,
"direction"
:
direction
,
"status_filter"
:
status_filter
,
"status_filter"
:
status_filter
,
"role_filter"
:
role_filter
"role_filter"
:
role_filter
,
"tier_filter"
:
tier_filter
,
"market_export_filter"
:
market_export_filter
,
}
}
}
}
)
)
...
...
templates/base.html
View file @
2be59d51
...
@@ -834,7 +834,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -834,7 +834,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<a
href=
"{{ url_for(request, '/dashboard/rotations') }}"
{%
if
'/
rotations
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.rotations"
>
Rotations
</a>
<a
href=
"{{ url_for(request, '/dashboard/rotations') }}"
{%
if
'/
rotations
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.rotations"
>
Rotations
</a>
<a
href=
"{{ url_for(request, '/dashboard/autoselect') }}"
{%
if
'/
autoselect
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.autoselect"
>
Autoselect
</a>
<a
href=
"{{ url_for(request, '/dashboard/autoselect') }}"
{%
if
'/
autoselect
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.autoselect"
>
Autoselect
</a>
<a
href=
"{{ url_for(request, '/dashboard/studio') }}"
{%
if
'/
studio
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.studio"
>
Studio
</a>
<a
href=
"{{ url_for(request, '/dashboard/studio') }}"
{%
if
'/
studio
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.studio"
>
Studio
</a>
{% if request.state.market_enabled is not defined or request.state.market_enabled %}
<a
href=
"{{ url_for(request, '/dashboard/market') }}"
{%
if
'/
market
'
in
request
.
path
and
'/
admin
/
market
'
not
in
request
.
path
%}
class=
"active"
{%
endif
%}
>
Market
</a>
<a
href=
"{{ url_for(request, '/dashboard/market') }}"
{%
if
'/
market
'
in
request
.
path
and
'/
admin
/
market
'
not
in
request
.
path
%}
class=
"active"
{%
endif
%}
>
Market
</a>
{% endif %}
<a
href=
"{{ url_for(request, '/dashboard/prompts') }}"
{%
if
'/
prompts
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.prompts"
>
Prompts
</a>
<a
href=
"{{ url_for(request, '/dashboard/prompts') }}"
{%
if
'/
prompts
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.prompts"
>
Prompts
</a>
<a
href=
"{{ url_for(request, '/dashboard/analytics') }}"
{%
if
'/
analytics
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.analytics"
>
Analytics
</a>
<a
href=
"{{ url_for(request, '/dashboard/analytics') }}"
{%
if
'/
analytics
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.analytics"
>
Analytics
</a>
{% if request.session.user_id %}
{% if request.session.user_id %}
...
@@ -846,7 +848,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -846,7 +848,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<a
href=
"{{ url_for(request, '/dashboard/users') }}"
{%
if
'/
users
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.users"
>
Users
</a>
<a
href=
"{{ url_for(request, '/dashboard/users') }}"
{%
if
'/
users
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.users"
>
Users
</a>
<a
href=
"{{ url_for(request, '/dashboard/settings') }}"
{%
if
'/
settings
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.settings"
>
Settings
</a>
<a
href=
"{{ url_for(request, '/dashboard/settings') }}"
{%
if
'/
settings
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.settings"
>
Settings
</a>
<a
href=
"{{ url_for(request, '/dashboard/admin/tiers') }}"
{%
if
'/
admin
/
tiers
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.tiers"
>
Tiers
</a>
<a
href=
"{{ url_for(request, '/dashboard/admin/tiers') }}"
{%
if
'/
admin
/
tiers
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.tiers"
>
Tiers
</a>
<a
href=
"{{ url_for(request, '/dashboard/admin/market') }}"
{%
if
'/
admin
/
market
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
>
Market Admin
</a>
<a
href=
"{{ url_for(request, '/dashboard/admin/payment-settings') }}"
{%
if
'/
admin
/
payment-settings
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.payment_settings"
>
Payment Settings
</a>
<a
href=
"{{ url_for(request, '/dashboard/admin/payment-settings') }}"
{%
if
'/
admin
/
payment-settings
'
in
request
.
path
%}
class=
"active"
{%
endif
%}
data-i18n=
"nav.payment_settings"
>
Payment Settings
</a>
{% endif %}
{% endif %}
{% if show_upgrade_button %}
{% if show_upgrade_button %}
...
...
templates/dashboard/admin_payment_settings.html
View file @
2be59d51
...
@@ -44,6 +44,50 @@
...
@@ -44,6 +44,50 @@
</form>
</form>
</div>
</div>
<div
style=
"background: var(--bg-panel); border: 2px solid var(--color-link); border-radius: 8px; padding: 20px; margin-bottom: 20px;"
>
<h3
style=
"margin: 0 0 20px 0; color: var(--color-link);"
>
<i
class=
"fas fa-store-alt me-2"
></i>
Market Administration
</h3>
<div
style=
"overflow:auto; border: 1px solid var(--color-border); border-radius: 8px; background: var(--bg-page);"
>
<table
style=
"width:100%; border-collapse: collapse;"
>
<thead>
<tr
style=
"background: var(--bg-accent);"
>
<th
style=
"padding:10px; text-align:left;"
>
ID
</th>
<th
style=
"padding:10px; text-align:left;"
>
Title
</th>
<th
style=
"padding:10px; text-align:left;"
>
Owner
</th>
<th
style=
"padding:10px; text-align:left;"
>
Source
</th>
<th
style=
"padding:10px; text-align:left;"
>
Status
</th>
<th
style=
"padding:10px; text-align:right;"
>
1M Tokens
</th>
<th
style=
"padding:10px; text-align:right;"
>
1K Requests
</th>
<th
style=
"padding:10px; text-align:right;"
>
Revenue
</th>
<th
style=
"padding:10px; text-align:right;"
>
Requests
</th>
<th
style=
"padding:10px; text-align:center;"
>
Active
</th>
</tr>
</thead>
<tbody>
{% for listing in market_listings %}
<tr
style=
"border-top:1px solid var(--color-border);"
>
<td
style=
"padding:10px;"
>
{{ listing.id }}
</td>
<td
style=
"padding:10px;"
>
{{ listing.title }}
</td>
<td
style=
"padding:10px;"
>
{{ listing.owner_username }}
</td>
<td
style=
"padding:10px;"
>
{{ listing.source_type }} / {{ listing.source_id }}
</td>
<td
style=
"padding:10px;"
>
{{ 'Online' if listing.online else 'Offline' }}
</td>
<td
style=
"padding:10px; text-align:right;"
>
{{ listing.price_per_million_tokens }}
</td>
<td
style=
"padding:10px; text-align:right;"
>
{{ listing.price_per_1000_requests }}
</td>
<td
style=
"padding:10px; text-align:right;"
>
{{ '%.2f'|format((listing.stats or {}).gross_revenue or 0) }}
</td>
<td
style=
"padding:10px; text-align:right;"
>
{{ (listing.stats or {}).total_requests or 0 }}
</td>
<td
style=
"padding:10px; text-align:center;"
>
{{ 'Yes' if listing.is_active else 'No' }}
</td>
</tr>
{% else %}
<tr>
<td
colspan=
"10"
style=
"padding:16px; text-align:center; color: var(--color-muted);"
>
No market listings yet.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Global Currency Settings -->
<!-- Global Currency Settings -->
<div
style=
"background: var(--bg-panel); border: 2px solid #17a2b8; border-radius: 8px; padding: 20px; margin-bottom: 20px;"
>
<div
style=
"background: var(--bg-panel); border: 2px solid #17a2b8; border-radius: 8px; padding: 20px; margin-bottom: 20px;"
>
<h3
style=
"margin: 0 0 20px 0; color: var(--color-link);"
>
<h3
style=
"margin: 0 0 20px 0; color: var(--color-link);"
>
...
...
templates/dashboard/users.html
View file @
2be59d51
...
@@ -82,6 +82,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -82,6 +82,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<option
value=
"user"
{%
if
filters
.
role_filter =
=
'
user
'
%}
selected
{%
endif
%}
>
User
</option>
<option
value=
"user"
{%
if
filters
.
role_filter =
=
'
user
'
%}
selected
{%
endif
%}
>
User
</option>
</select>
</select>
<label
for=
"tier-filter"
style=
"color: var(--color-text); margin-left: 10px; margin-right: 5px;"
data-i18n=
"users_page.col_tier"
>
Tier:
</label>
<select
id=
"tier-filter"
style=
"padding: 8px; border-radius: 4px; border: 1px solid var(--bg-accent); background: var(--bg-page); color: var(--color-text);"
>
<option
value=
""
>
All
</option>
{% for tier in tiers %}
<option
value=
"{{ tier.id }}"
{%
if
filters
.
tier_filter =
=
tier
.
id
|
string
%}
selected
{%
endif
%}
>
{{ tier.name }}{% if not tier.is_visible %} (Hidden){% endif %}
</option>
{% endfor %}
</select>
<label
for=
"market-export-filter"
style=
"color: var(--color-text); margin-left: 10px; margin-right: 5px;"
>
Market:
</label>
<select
id=
"market-export-filter"
style=
"padding: 8px; border-radius: 4px; border: 1px solid var(--bg-accent); background: var(--bg-page); color: var(--color-text);"
>
<option
value=
""
>
All
</option>
<option
value=
"exporting"
{%
if
filters
.
market_export_filter =
=
'
exporting
'
%}
selected
{%
endif
%}
>
Exporting
</option>
<option
value=
"not_exporting"
{%
if
filters
.
market_export_filter =
=
'
not_exporting
'
%}
selected
{%
endif
%}
>
Not Exporting
</option>
</select>
<button
id=
"search-btn"
class=
"btn"
style=
"padding: 8px 16px;"
data-i18n=
"users_page.search_btn"
>
Search
</button>
<button
id=
"search-btn"
class=
"btn"
style=
"padding: 8px 16px;"
data-i18n=
"users_page.search_btn"
>
Search
</button>
<button
id=
"clear-btn"
class=
"btn btn-secondary"
style=
"padding: 8px 16px;"
data-i18n=
"users_page.clear_btn"
>
Clear
</button>
<button
id=
"clear-btn"
class=
"btn btn-secondary"
style=
"padding: 8px 16px;"
data-i18n=
"users_page.clear_btn"
>
Clear
</button>
</div>
</div>
...
@@ -370,6 +387,10 @@ document.addEventListener('DOMContentLoaded', function() {
...
@@ -370,6 +387,10 @@ document.addEventListener('DOMContentLoaded', function() {
const
searchInput
=
document
.
getElementById
(
'search-input'
);
const
searchInput
=
document
.
getElementById
(
'search-input'
);
const
searchBtn
=
document
.
getElementById
(
'search-btn'
);
const
searchBtn
=
document
.
getElementById
(
'search-btn'
);
const
clearBtn
=
document
.
getElementById
(
'clear-btn'
);
const
clearBtn
=
document
.
getElementById
(
'clear-btn'
);
const
statusFilter
=
document
.
getElementById
(
'status-filter'
);
const
roleFilter
=
document
.
getElementById
(
'role-filter'
);
const
tierFilter
=
document
.
getElementById
(
'tier-filter'
);
const
marketExportFilter
=
document
.
getElementById
(
'market-export-filter'
);
const
pageSize
=
document
.
getElementById
(
'page-size'
);
const
pageSize
=
document
.
getElementById
(
'page-size'
);
const
prevBtn
=
document
.
getElementById
(
'prev-btn'
);
const
prevBtn
=
document
.
getElementById
(
'prev-btn'
);
const
nextBtn
=
document
.
getElementById
(
'next-btn'
);
const
nextBtn
=
document
.
getElementById
(
'next-btn'
);
...
@@ -543,19 +564,54 @@ document.addEventListener('DOMContentLoaded', function() {
...
@@ -543,19 +564,54 @@ document.addEventListener('DOMContentLoaded', function() {
clearBtn
.
addEventListener
(
'click'
,
function
()
{
clearBtn
.
addEventListener
(
'click'
,
function
()
{
if
(
searchInput
)
{
if
(
searchInput
)
{
searchInput
.
value
=
''
;
searchInput
.
value
=
''
;
updateUsers
({
search
:
''
,
page
:
1
});
}
}
if
(
statusFilter
)
{
statusFilter
.
value
=
''
;
}
if
(
roleFilter
)
{
roleFilter
.
value
=
''
;
}
if
(
tierFilter
)
{
tierFilter
.
value
=
''
;
}
if
(
marketExportFilter
)
{
marketExportFilter
.
value
=
''
;
}
updateUsers
({
search
:
''
,
status_filter
:
null
,
role_filter
:
null
,
tier_filter
:
null
,
market_export_filter
:
null
,
page
:
1
,
});
});
});
}
}
// Filter change handlers
// Filter change handlers
document
.
getElementById
(
'status-filter'
).
addEventListener
(
'change'
,
function
()
{
if
(
statusFilter
)
{
updateUsers
({
status_filter
:
this
.
value
||
null
});
statusFilter
.
addEventListener
(
'change'
,
function
()
{
});
updateUsers
({
status_filter
:
this
.
value
||
null
});
});
}
document
.
getElementById
(
'role-filter'
).
addEventListener
(
'change'
,
function
()
{
if
(
roleFilter
)
{
updateUsers
({
role_filter
:
this
.
value
||
null
});
roleFilter
.
addEventListener
(
'change'
,
function
()
{
});
updateUsers
({
role_filter
:
this
.
value
||
null
});
});
}
if
(
tierFilter
)
{
tierFilter
.
addEventListener
(
'change'
,
function
()
{
updateUsers
({
tier_filter
:
this
.
value
||
null
});
});
}
if
(
marketExportFilter
)
{
marketExportFilter
.
addEventListener
(
'change'
,
function
()
{
updateUsers
({
market_export_filter
:
this
.
value
||
null
});
});
}
// Initial sorting listeners
// Initial sorting listeners
attachSortingListeners
();
attachSortingListeners
();
...
@@ -1070,4 +1126,4 @@ document.addEventListener('DOMContentLoaded', function() {
...
@@ -1070,4 +1126,4 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
});
});
</script>
</script>
{% endblock %}
{% endblock %}
\ No newline at end of file
tests/routes/test_dashboard_user_market_controls.py
0 → 100644
View file @
2be59d51
This diff is collapsed.
Click to expand it.
tests/test_database_user_market_filters.py
0 → 100644
View file @
2be59d51
import
pytest
from
aisbf.database
import
DatabaseManager
@
pytest
.
fixture
def
db_manager
(
tmp_path
):
db_path
=
tmp_path
/
"users.db"
db
=
DatabaseManager
({
'type'
:
'sqlite'
,
'sqlite_path'
:
str
(
db_path
),
})
pro_tier_id
=
db
.
create_tier
(
name
=
'Pro Tier'
,
description
=
'Paid plan'
,
price_monthly
=
10.0
,
price_yearly
=
100.0
,
)
users
=
{
'alice'
:
db
.
create_user
(
'alice'
,
'hash'
,
role
=
'user'
,
email
=
'alice@example.com'
,
display_name
=
'Alice Exporter'
,
),
'bob'
:
db
.
create_user
(
'bob'
,
'hash'
,
role
=
'user'
,
email
=
'bob@example.com'
,
display_name
=
'Bob Browser'
,
),
'carol'
:
db
.
create_user
(
'carol'
,
'hash'
,
role
=
'admin'
,
email
=
'carol@example.com'
,
display_name
=
'Carol Admin'
,
),
'dave'
:
db
.
create_user
(
'dave'
,
'hash'
,
role
=
'user'
,
email
=
'dave@example.com'
,
display_name
=
'Dave Disabled'
,
),
}
with
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
cursor
.
execute
(
'UPDATE users SET tier_id = ? WHERE id = ?'
,
(
pro_tier_id
,
users
[
'alice'
]),
)
cursor
.
execute
(
'UPDATE users SET is_active = 0 WHERE id = ?'
,
(
users
[
'dave'
],),
)
conn
.
commit
()
db
.
upsert_market_listing
(
users
[
'alice'
],
'alice'
,
{
'source_scope'
:
'user'
,
'source_type'
:
'provider'
,
'source_id'
:
'alice-provider'
,
'listing_key'
:
'provider:alice-provider'
,
'title'
:
'Alice Provider'
,
'description'
:
'Alice export listing'
,
'provider_id'
:
'alice-provider'
,
'model_id'
:
None
,
'endpoint'
:
'https://example.test/alice'
,
'currency_code'
:
'USD'
,
'price_per_million_tokens'
:
2.5
,
'price_per_1000_requests'
:
0.0
,
'provider_price_per_million_tokens'
:
2.5
,
'provider_price_per_1000_requests'
:
0.0
,
'metadata'
:
{
'provider_type'
:
'openai'
},
'config_snapshot'
:
{
'provider'
:
{
'type'
:
'openai'
}},
'is_active'
:
True
,
},
)
db
.
upsert_market_listing
(
users
[
'carol'
],
'carol'
,
{
'source_scope'
:
'user'
,
'source_type'
:
'provider'
,
'source_id'
:
'carol-provider'
,
'listing_key'
:
'provider:carol-provider'
,
'title'
:
'Carol Provider'
,
'description'
:
'Carol export listing'
,
'provider_id'
:
'carol-provider'
,
'model_id'
:
None
,
'endpoint'
:
'https://example.test/carol'
,
'currency_code'
:
'USD'
,
'price_per_million_tokens'
:
4.0
,
'price_per_1000_requests'
:
0.0
,
'provider_price_per_million_tokens'
:
4.0
,
'provider_price_per_1000_requests'
:
0.0
,
'metadata'
:
{
'provider_type'
:
'openai'
},
'config_snapshot'
:
{
'provider'
:
{
'type'
:
'openai'
}},
'is_active'
:
True
,
},
)
db
.
upsert_market_listing
(
users
[
'dave'
],
'dave'
,
{
'source_scope'
:
'user'
,
'source_type'
:
'provider'
,
'source_id'
:
'dave-provider'
,
'listing_key'
:
'provider:dave-provider'
,
'title'
:
'Dave Provider'
,
'description'
:
'Dave inactive export listing'
,
'provider_id'
:
'dave-provider'
,
'model_id'
:
None
,
'endpoint'
:
'https://example.test/dave'
,
'currency_code'
:
'USD'
,
'price_per_million_tokens'
:
1.0
,
'price_per_1000_requests'
:
0.0
,
'provider_price_per_million_tokens'
:
1.0
,
'provider_price_per_1000_requests'
:
0.0
,
'metadata'
:
{
'provider_type'
:
'openai'
},
'config_snapshot'
:
{
'provider'
:
{
'type'
:
'openai'
}},
'is_active'
:
False
,
},
)
return
{
'db'
:
db
,
'pro_tier_id'
:
pro_tier_id
,
}
def
_usernames
(
result
):
return
[
user
[
'username'
]
for
user
in
result
[
'users'
]]
def
test_get_users_paginated_filters_by_tier_id
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
tier_filter
=
db_manager
[
'pro_tier_id'
],
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
1
assert
_usernames
(
result
)
==
[
'alice'
]
def
test_get_users_paginated_nonexistent_tier_id_returns_no_matches
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
tier_filter
=
999999
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
0
assert
_usernames
(
result
)
==
[]
def
test_get_users_paginated_filters_users_with_market_exports
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
market_export_filter
=
'exporting'
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
2
assert
_usernames
(
result
)
==
[
'alice'
,
'carol'
]
def
test_get_users_paginated_filters_users_without_market_exports
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
market_export_filter
=
'not_exporting'
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
2
assert
_usernames
(
result
)
==
[
'bob'
,
'dave'
]
def
test_get_users_paginated_ignores_inactive_only_market_exports
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
market_export_filter
=
'exporting'
,
search
=
'dave'
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
0
assert
_usernames
(
result
)
==
[]
def
test_get_users_paginated_unsupported_market_export_filter_falls_back_to_unfiltered
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
market_export_filter
=
'maybe'
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
4
assert
_usernames
(
result
)
==
[
'alice'
,
'bob'
,
'carol'
,
'dave'
]
def
test_get_users_paginated_combines_market_export_and_existing_filters
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
search
=
'alice'
,
status_filter
=
'active'
,
role_filter
=
'user'
,
market_export_filter
=
'exporting'
,
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
1
assert
_usernames
(
result
)
==
[
'alice'
]
def
test_get_users_paginated_combines_tier_and_existing_filters
(
db_manager
):
result
=
db_manager
[
'db'
]
.
get_users_paginated
(
search
=
'alice'
,
status_filter
=
'active'
,
role_filter
=
'user'
,
tier_filter
=
db_manager
[
'pro_tier_id'
],
order_by
=
'username'
,
direction
=
'asc'
,
)
assert
result
[
'total'
]
==
1
assert
_usernames
(
result
)
==
[
'alice'
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment