Skip to content

Commit e4a7a01

Browse files
fix: Use auto-approved users allowlist for queue eligibility (#7156)
1 parent 35c2d3d commit e4a7a01

4 files changed

Lines changed: 203 additions & 312 deletions

File tree

.github/workflows/auto-update-db.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ jobs:
9595
if: github.event.label.name == 'request-theme'
9696
env:
9797
ISSUE_AUTHOR_USER_ID: ${{ github.event.issue.user.id }}
98-
run: uv run --no-sync python -m src.queue_eligibility --submission-file submission.json --database-root database
98+
run: uv run --no-sync python -m src.queue_eligibility
9999

100100
- name: Update
101101
id: update

auto_approved_users.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"user_id": 30657709,
4+
"username": "LuckyNoS7evin"
5+
},
6+
{
7+
"user_id": 42013603,
8+
"username": "ReenigneArcher"
9+
},
10+
{
11+
"user_id": 88998541,
12+
"username": "jjnether"
13+
}
14+
]

src/queue_eligibility.py

Lines changed: 55 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,10 @@
66
import os
77
from dataclasses import dataclass
88
from pathlib import Path
9-
from urllib.parse import urlparse
9+
from typing import Iterable
1010

1111

12-
CONTRIBUTION_THRESHOLD = 15
13-
IGDB_HOST = 'igdb.com'
14-
TMDB_HOST = 'themoviedb.org'
15-
16-
CATEGORY_BY_HOST_AND_PATH = {
17-
(IGDB_HOST, 'games'): 'games',
18-
(IGDB_HOST, 'collections'): 'game_collections',
19-
(IGDB_HOST, 'franchises'): 'game_franchises',
20-
(TMDB_HOST, 'movie'): 'movies',
21-
(TMDB_HOST, 'collection'): 'movie_collections',
22-
(TMDB_HOST, 'tv'): 'tv_shows',
23-
}
12+
AUTO_APPROVED_USERS_FILE = 'auto_approved_users.json'
2413

2514

2615
@dataclass(frozen=True)
@@ -32,169 +21,102 @@ class QueueEligibilityResult:
3221
----------
3322
queue_eligible : bool
3423
Whether the submission should receive the queue label.
35-
category : str
36-
Database category derived from the submission URL.
37-
contribution_count : int
38-
Existing contribution count for the submitter in the derived category.
24+
user_id : str
25+
GitHub user id of the issue author.
3926
reason : str
4027
Machine-readable reason for the eligibility decision.
4128
"""
4229

4330
queue_eligible: bool
44-
category: str
45-
contribution_count: int
31+
user_id: str
4632
reason: str
4733

4834

49-
def get_submission_category(database_url: str) -> str:
35+
def normalize_user_id(user_id: object) -> str:
5036
"""
51-
Return the database category for a submission URL.
37+
Normalize a GitHub user id for matching.
5238
5339
Parameters
5440
----------
55-
database_url : str
56-
Database URL from the issue submission.
41+
user_id : object
42+
GitHub user id to normalize.
5743
5844
Returns
5945
-------
6046
str
61-
Top-level database category directory.
62-
63-
Raises
64-
------
65-
ValueError
66-
If the URL cannot be mapped to a known database category.
67-
"""
68-
parsed_url = urlparse(database_url.strip())
69-
hostname = parsed_url.hostname or ''
70-
hostname = hostname.removeprefix('www.')
71-
path_parts = [part for part in parsed_url.path.split('/') if part]
72-
73-
if not path_parts:
74-
raise ValueError(f'Unsupported database URL: {database_url}')
75-
76-
category = CATEGORY_BY_HOST_AND_PATH.get((hostname, path_parts[0]))
77-
if not category:
78-
raise ValueError(f'Unsupported database URL: {database_url}')
79-
80-
return category
81-
82-
83-
def get_contribution_count(contributor_data: dict, user_id: str) -> int:
84-
"""
85-
Return a contributor's total contributions from contributor metadata.
86-
87-
Parameters
88-
----------
89-
contributor_data : dict
90-
Parsed contents of a category ``contributors.json`` file.
91-
user_id : str
92-
GitHub user ID to look up.
93-
94-
Returns
95-
-------
96-
int
97-
Sum of the contributor's added and edited item counts, or zero when the
98-
metadata is missing or malformed.
47+
Trimmed user id string.
9948
"""
100-
if not isinstance(contributor_data, dict):
101-
return 0
49+
if user_id is None:
50+
return ''
10251

103-
contributor = contributor_data.get(str(user_id), {})
104-
if not isinstance(contributor, dict):
105-
return 0
52+
return str(user_id).strip()
10653

107-
try:
108-
return int(contributor.get('items_added', 0)) + int(contributor.get('items_edited', 0))
109-
except (TypeError, ValueError):
110-
return 0
11154

112-
113-
def load_contribution_count(database_root: Path, category: str, user_id: str) -> int:
55+
def load_auto_approved_user_ids(auto_approved_users_file: Path) -> frozenset[str]:
11456
"""
115-
Load a contributor's category contribution count from disk.
57+
Load auto-approved GitHub user ids from a JSON file.
11658
11759
Parameters
11860
----------
119-
database_root : pathlib.Path
120-
Root directory containing category database folders.
121-
category : str
122-
Category directory to inspect.
123-
user_id : str
124-
GitHub user ID to look up.
61+
auto_approved_users_file : pathlib.Path
62+
JSON file containing auto-approved user objects.
12563
12664
Returns
12765
-------
128-
int
129-
Contributor count for the category, or zero when the contributors file
130-
is absent or unreadable.
66+
frozenset[str]
67+
Normalized GitHub user ids.
13168
"""
132-
contributors_file = database_root / category / 'contributors.json'
133-
if not contributors_file.exists():
134-
return 0
135-
13669
try:
137-
with contributors_file.open() as contributor_f:
138-
contributor_data = json.load(contributor_f)
70+
with auto_approved_users_file.open(encoding='utf-8') as auto_approved_users_f:
71+
auto_approved_users = json.load(auto_approved_users_f)
13972
except (OSError, json.JSONDecodeError):
140-
return 0
73+
return frozenset()
74+
75+
if not isinstance(auto_approved_users, list):
76+
return frozenset()
14177

142-
return get_contribution_count(contributor_data=contributor_data, user_id=user_id)
78+
return frozenset(
79+
normalized_user_id
80+
for user in auto_approved_users
81+
if isinstance(user, dict)
82+
if (normalized_user_id := normalize_user_id(user.get('user_id', '')))
83+
)
14384

14485

145-
def evaluate_queue_eligibility(submission: dict,
146-
database_root: Path,
147-
user_id: str,
148-
threshold: int = CONTRIBUTION_THRESHOLD) -> QueueEligibilityResult:
86+
def evaluate_queue_eligibility(user_id: object,
87+
auto_approved_user_ids: Iterable[object]) -> QueueEligibilityResult:
14988
"""
15089
Evaluate whether a submission is eligible to be queued automatically.
15190
15291
Parameters
15392
----------
154-
submission : dict
155-
Parsed issue submission data.
156-
database_root : pathlib.Path
157-
Root directory containing category database folders.
158-
user_id : str
159-
GitHub user ID of the issue author.
160-
threshold : int, optional
161-
Minimum prior category contribution count that must be exceeded.
93+
user_id : object
94+
GitHub user id of the issue author.
95+
auto_approved_user_ids : Iterable[object]
96+
GitHub user ids eligible for automatic queueing.
16297
16398
Returns
16499
-------
165100
QueueEligibilityResult
166101
Eligibility decision and supporting metadata.
167102
"""
168-
if not user_id:
103+
normalized_user_id = normalize_user_id(user_id=user_id)
104+
if not normalized_user_id:
169105
return QueueEligibilityResult(
170106
queue_eligible=False,
171-
category='',
172-
contribution_count=0,
107+
user_id='',
173108
reason='missing-user-id',
174109
)
175110

176-
try:
177-
category = get_submission_category(database_url=submission['database_url'])
178-
except (KeyError, ValueError):
179-
return QueueEligibilityResult(
180-
queue_eligible=False,
181-
category='',
182-
contribution_count=0,
183-
reason='unsupported-category',
184-
)
185-
186-
contribution_count = load_contribution_count(
187-
database_root=database_root,
188-
category=category,
189-
user_id=user_id,
190-
)
191-
queue_eligible = contribution_count > threshold
192-
reason = 'eligible' if queue_eligible else 'below-threshold'
111+
queue_eligible = normalized_user_id in {
112+
normalize_user_id(user_id=auto_approved_user_id)
113+
for auto_approved_user_id in auto_approved_user_ids
114+
}
115+
reason = 'auto-approved-user' if queue_eligible else 'not-auto-approved-user'
193116

194117
return QueueEligibilityResult(
195118
queue_eligible=queue_eligible,
196-
category=category,
197-
contribution_count=contribution_count,
119+
user_id=normalized_user_id,
198120
reason=reason,
199121
)
200122

@@ -214,8 +136,7 @@ def write_github_outputs(result: QueueEligibilityResult) -> None:
214136
"""
215137
outputs = {
216138
'queue_eligible': str(result.queue_eligible).lower(),
217-
'category': result.category,
218-
'contribution_count': str(result.contribution_count),
139+
'user_id': result.user_id,
219140
'reason': result.reason,
220141
}
221142
output_lines = [f'{key}={value}' for key, value in outputs.items()]
@@ -239,9 +160,7 @@ def parse_args() -> argparse.Namespace:
239160
Parsed command-line arguments.
240161
"""
241162
parser = argparse.ArgumentParser(description='Check whether a submission is eligible for queueing.')
242-
parser.add_argument('--submission-file', default='submission.json')
243-
parser.add_argument('--database-root', default='database')
244-
parser.add_argument('--threshold', type=int, default=CONTRIBUTION_THRESHOLD)
163+
parser.add_argument('--auto-approved-users-file', default=AUTO_APPROVED_USERS_FILE)
245164
parser.add_argument('--user-id', default=os.environ.get('ISSUE_AUTHOR_USER_ID', ''))
246165
return parser.parse_args()
247166

@@ -255,24 +174,13 @@ def main() -> None:
255174
None
256175
"""
257176
args = parse_args()
258-
259-
try:
260-
with open(args.submission_file) as submission_f:
261-
submission = json.load(submission_f)
262-
except (OSError, json.JSONDecodeError):
263-
result = QueueEligibilityResult(
264-
queue_eligible=False,
265-
category='',
266-
contribution_count=0,
267-
reason='submission-read-error',
268-
)
269-
else:
270-
result = evaluate_queue_eligibility(
271-
submission=submission,
272-
database_root=Path(args.database_root),
273-
user_id=args.user_id,
274-
threshold=args.threshold,
275-
)
177+
auto_approved_user_ids = load_auto_approved_user_ids(
178+
auto_approved_users_file=Path(args.auto_approved_users_file),
179+
)
180+
result = evaluate_queue_eligibility(
181+
user_id=args.user_id,
182+
auto_approved_user_ids=auto_approved_user_ids,
183+
)
276184
write_github_outputs(result=result)
277185

278186

0 commit comments

Comments
 (0)