Repository navigation
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
cejiogu
left a comment
There was a problem hiding this comment.
Left some comments, but on a more general note, can you either change this PR's base to claire/deleteUser (so it’s a real stack), or rebase it onto master with only its own changes.
Currently, this PR also merges delete-user and the favorites removal, which still has changes requested on PR #83
| class ReportQuery(ObjectType): | ||
| """Read operations for anonymous Score reports.""" | ||
|
|
||
| reports = List( |
There was a problem hiding this comment.
How does reports differ from get_all_reports? From their description alone, they seem like they're the same field, and so you should get rid of one.
|
|
||
| if issue not in REPORT_ISSUES: | ||
| raise GraphQLError("Issue is not a valid report category.") | ||
| if description and len(description) > 5000: |
There was a problem hiding this comment.
NIT: Can we make 5000 a constant in utils and then reference it here? Emphasis is reusability here; a limit of 5000 characters could be relevant beyond just this context
| import gspread | ||
|
|
||
| client = gspread.service_account(filename=credentials_path) | ||
| sheet_name = os.getenv("BUG_REPORT_SHEET_NAME", "Bug Reports") |
There was a problem hiding this comment.
Let's make the "Bug Reports" fallback a constant and include it in utils
| import gspread | ||
|
|
||
| client = gspread.service_account(filename=credentials_path) | ||
| sheet_name = os.getenv("BUG_REPORT_SHEET_NAME", "Bug Reports") |
There was a problem hiding this comment.
Structurally, this looks good Claire, but how are we handling authentication in this case? As of now, this file permits anyone to delete any report, which is something we wouldn't want. Look into including an admin check on deleteReport
| from src.types import ReportType | ||
|
|
||
|
|
||
| class ReportQuery(ObjectType): |
There was a problem hiding this comment.
On a related note to this comment, this query essentially allows anyone to read any report — which would again be something we wouldn't want. Look into putting an admin check/auth on this query too
I think Uplift had a similar reports problem and is about to put out a PR to implement this solution. Consider basing your work off of theirs if their PR comes out before you get to this
| return report | ||
|
|
||
| @staticmethod | ||
| def _mirror_to_google_sheet(report): |
There was a problem hiding this comment.
Especially since it seems that our database is our source of truth — with the Google Sheets just being used as a convenient interface instead — look into making this call a background job/asynchronous.
Currently, whenever the CreateReport mutation is called, we call this method within it, but this could delay our API's response to the client (each create_report call authenticates to Google, opens the sheet and appends a row before responding, which takes additional time).
| worksheet = client.open_by_key(sheet_id).worksheet(sheet_name) | ||
| try: | ||
| cell = worksheet.find(str(report_id), in_column=1) | ||
| except gspread.exceptions.CellNotFound: |
There was a problem hiding this comment.
Claude checked for gspread.exceptions.CellNotFound, and it doesn't exist in gspread 6.x., so this is dead code. Not sure what testing looked like for this specific exception, but suspect it only “worked” because the outer except Exception catches the resulting AttributeError
andrewp0809
left a comment
There was a problem hiding this comment.
Added two more comments. My other findings are either already in Chimdi's comments or came from the last master merge, and a clean rebase should fix those. Just keep the favorites/bookmarks mutations in the src.mutations import when resolving the schema.py conflict.
| """Business logic for creating and managing anonymous reports.""" | ||
|
|
||
| @staticmethod | ||
| def create_report(issue, sport=None, description=None): |
There was a problem hiding this comment.
Since createReport doesn't require auth, anyone could spam it and fill up the collection and our Sheets API quota. Look into adding a rate limit, or auth if the client supports it.
| @staticmethod | ||
| def _remove_from_google_sheet(report_id): | ||
| """Best-effort removal from the optional Google Sheets mirror.""" | ||
| sheet_id = os.getenv("BUG_REPORT_SHEET_ID") or os.getenv("SHEET_KEY") |
There was a problem hiding this comment.
Minor, not blocking: _mirror_to_google_sheet and _remove_from_google_sheet have the same setup code. Can we pull it into a _get_worksheet() helper and cache the client so we're not logging in to Google on every call?
AnikDey-exe
left a comment
There was a problem hiding this comment.
Good PR overall. There are someNameErrors that have to be fixed and I think we should use a separate Google Sheets key to avoid giving the Firebase key access beyond what it should have.
| @@ -1,5 +1,4 @@ | |||
| from bson import ObjectId | |||
| from pymongo import ReturnDocument | |||
There was a problem hiding this comment.
The last master merge dropped two imports whose call sites are still there, so parts of the app raise NameError at runtime. Andrew's comment about the schema.py conflict wheresrc/schema.py now imports AddFavoriteTeam/RemoveFavoriteTeam/AddBookmarkedHighlight/RemoveBookmarkedHighlight from src.queries, which doesn't export them. That one is an ImportError at startup, so the app won't boot at all).
| from flask_jwt_extended import get_jwt_identity | ||
| from graphene import Boolean, ObjectType, String, Field, List, Int, DateTime | ||
| from src.services.game_service import GameService | ||
| from src.types import GameType |
There was a problem hiding this comment.
This will cause aNameError at runtime. Same problem as user_repository.py: this merge deleted from flask_jwt_extended import get_jwt_identity from line 1, but it's still called at line 41 inside the @graphql_jwt_required() resolver.
Also note this import added a second from src.types import GameType on this line while line 5 already imports it alongside FavoriteTeamType. Drop the duplicate and put the flask_jwt_extended import back.
| def _mirror_to_google_sheet(report): | ||
| """Best-effort sheet mirroring; the database remains the source of truth.""" | ||
| sheet_id = os.getenv("BUG_REPORT_SHEET_ID") or os.getenv("SHEET_KEY") | ||
| credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") or os.getenv( |
There was a problem hiding this comment.
GOOGLE_APPLICATION_CREDENTIALS is the Firebase Admin service-account key. app.py:33 reads the same variable and initialize_firebase() refuses to start without it. Two consequences worth thinking about:
- The
if not sheet_id or not credentials_path: returnguard is justif not sheet_id, sincecredentials_pathis always set. - I think to make it actually work you'd have to share the spreadsheet with the Firebase Admin service account, which widens what that key can reach beyond Firebase.
A dedicated BUG_REPORT_SHEET_CREDENTIALS (or at least a separate service account with only Sheets scope) separates Firebase key permissions from Google Sheet functionalities.
Overview
Implemented anonymous report functionality with GraphQL support for creating, querying, and deleting reports.
Changes
GraphQL API
createReport(issue:, sport:, description:)issueis required.sportanddescriptionare optional.getAllReports(limit:, offset:)reports(limit:, offset:)deleteReport(reportId:)Report Categories
Supported issue categories:
INACCURATE_SCORESWRONG_GAME_TIMEMISSING_GAMEOTHERGoogle Sheets Integration
Reports are mirrored to Google Sheets when
BUG_REPORT_SHEET_IDandGOOGLE_APPLICATION_CREDENTIALSare configured.The sheet should contain a tab named
Bug Reports, unless another name is provided throughBUG_REPORT_SHEET_NAME.Each row includes:
MongoDB remains the source of truth, so Google Sheets errors do not prevent reports from being saved.
Testing
Manually tested
createReport,getAllReports, anddeleteReportusing GraphQL Playground.