Skip to content

Add report functionality with creation, deletion, and querying capabilities - #85

Open
claiireyu wants to merge 3 commits into
masterfrom
claire/bugReport
Open

claiireyu wants to merge 3 commits into
masterfrom
claire/bugReport

Conversation

@claiireyu

@claiireyu claiireyu commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Implemented anonymous report functionality with GraphQL support for creating, querying, and deleting reports.

Changes

GraphQL API

  • createReport(issue:, sport:, description:)

    • Creates a report in MongoDB.
    • issue is required.
    • sport and description are optional.
  • getAllReports(limit:, offset:)

    • Retrieves reports in reverse chronological order.
  • reports(limit:, offset:)

    • Provides an alternate query for retrieving reports.
  • deleteReport(reportId:)

    • Deletes a report from MongoDB.

Report Categories

Supported issue categories:

  • INACCURATE_SCORES
  • WRONG_GAME_TIME
  • MISSING_GAME
  • OTHER

Google Sheets Integration

Reports are mirrored to Google Sheets when BUG_REPORT_SHEET_ID and GOOGLE_APPLICATION_CREDENTIALS are configured.

The sheet should contain a tab named Bug Reports, unless another name is provided through BUG_REPORT_SHEET_NAME.

Each row includes:

  • Report ID
  • Issue
  • Sport
  • Description
  • Timestamp

MongoDB remains the source of truth, so Google Sheets errors do not prevent reports from being saved.

Testing

Manually tested createReport, getAllReports, and deleteReport using GraphQL Playground.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 20ac2dad-ef29-4a2b-91a6-768338add943
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@cejiogu cejiogu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here

Comment thread src/mutations/report.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 andrewp0809 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AnikDey-exe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread src/queries/game_query.py
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. The if not sheet_id or not credentials_path: return guard is just if not sheet_id, since credentials_path is always set.
  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants