Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

src/

This directory holds the source code for the Codebloom backend, which is written in Java and Spring Boot.

The backend is currently running on Java 25 due to the fact that virtual threads would be quite useful for us since:

  • our production machine doesn't have many threads (1 vCPU, 2 threads)
  • a lot of our backend complexity comes from background processes such as timely leaderboard syncs and long-lived Postgres [NOTIFY/LISTEN] logic for pub-sub.

View the public Swagger endpoint in production at codebloom.patinanetwork.org/swagger-ui/index.html

View STYLEGUIDE.md to view best practices.

Requirements

Warning

It is HIGHLY recommended that you follow the setup instructions at docs/local/README.md to get up and running.

  • openjdk@25 (though you may feel free to use any other distribution if you would like)
  • mvn - you do not need to explicitly download this, just use ./mvnw from the root directory instead (our Justfile commands all use ./mvnw as well).

Run

Warning

It is HIGHLY recommended that you follow the setup instructions at docs/local/README.md to get up and running.

Structure

Note

The backend is very large, so many classes are merged into a package instead to save space for the high-level structure overview

src/main
├── test                                                            # Holds all tests
├── java
│   └── org
│       └── patinanetwork
│           └── codebloom
│               ├── api                                             # holds all api specific classes (controllers, request bodies, custom validators, etc)
│               ├── CodebloomApplication.java                       # spring boot entrypoint
│               ├── common
│               │   ├── components                                  # managers that are responsible for complex behaviors (e.g. DiscordClubManager, DuelManager, etc.)
│               │   ├── db
│               │   │   ├── helper                                  # helper utils
│               │   │   ├── models                                  # db model objects
│               │   │   └── repos                                   # db repository classes
│               │   ├── dto                                         # dtos
│               │   ├── email
│               │   │   ├── client
│               │   │   │   ├── codebloom                           # used to send emails
│               │   │   │   └── github                              # used to read emails during Leetcode OAuth process
│               │   │   ├── Email.java                              # base interface used by ./client
│               │   │   ├── error                                   # shared error used by ./client
│               │   │   ├── Message.java                            # base message object used by ./client
│               │   │   ├── options                                 # shared builder objects used by ./client
│               │   │   └── template                                # react email templater
│               │   ├── jwt                                         # jwt helper class(es)
│               │   ├── leetcode                                    # leetcode client classes
│               │   │   ├── LeetcodeClient.java
│               │   │   ├── LeetcodeClientImpl.java
│               │   │   ├── models                                  # shared objects belonging to leetcode.com
│               │   │   ├── queries                                 # GraphQL queries stored as strings
│               │   │   ├── score                                   # scoring logic that Codebloom uses
│               │   │   └── throttled                               # throttled to help alleviate rate limits
│               │   │       ├── ThrottledLeetcodeClient.java
│               │   │       └── ThrottledLeetcodeClientImpl.java
│               │   ├── redis                                       # WIP: redis
│               │   ├── reporter                                    # classes that can be used to report logs & errors to our private Discord server
│               │   ├── schools                                     # school-specific utils
│               │   ├── security                                    # security/auth helper utils
│               │   ├── simpleredis                                 # in-memory redis
│               │   ├── submissions                                 # leetcode/codebloom submission glue code handler
│               │   ├── time                                        # time utils
│               │   ├── url
│               │   │   └── ServerUrlUtils.java
│               │   └── utils                                       # more utils
│               ├── jda
│               │   ├── client
│               │   │   ├── JDAClient.java                          # external client that can be consumed by Spring Boot
│               │   ├── command                                     # slash command logic
│               │   ├── JDAClientManager.java                       # wires up JDAClient
│               │   ├── JDACommandRegisterHandler.java              # registers slash commands
│               │   └── properties
│               ├── playwright                                      # external playwright client
│               │   └── PlaywrightClient.java
│               ├── scheduled                                       # holds all background processes
│               │   ├── auth                                        # used to "steal" a leetcode token. this is a special class as runs in the bg and can be imported into other classes (this separation should be more distinct in the future)
│               │   ├── discord                                     # used to send weekly leaderboard to discord
│               │   ├── duel                                        # WIP: used to cleanup expired duels
│               │   ├── leetcode                                    # various bg services related to leetcode.com and Codebloom
│               │   ├── pg                                          # postgres NOTIFY/LISTEN handlers
│               │   │   ├── handler
│               │   │   │   ├── JobNotifyHandler.java
│               │   │   │   └── LobbyNotifyHandler.java
│               │   │   ├── NotifyListener.java                     # main class where notifications are checked
│               │   ├── potd                                        # update POTD
│               │   └── submission                                  # routinely check for new submissions
│               ├── shared                                          # WIP: still being worked out but currently used to define complex shared enum logic that can be used in the backend but also auto-generated to the frontend
│               │   └── tag
│               │       └── ParentTags.java
│               └── utilities
│                   ├── exception                                   # exception handlers
│                   ├── generator                                   # complex generator logic, such as automatic typescript file generation, react email generation, etc
│                   ├── OpenApiConfig.java                          # force loads some routes to OpenAPI spec
│                   ├── RateLimitingFilter.java                     # handles rate limiting
│                   ├── ServerMetadataObject.java                   # small object returned when /api called
│                   ├── sha                                         # object that returns the last committed SHA at runtime
│                   ├── StaticContentFilter.java                    # handles static content vs dynamic routes
│                   └── WebConfig.java                              # used to load some custom annotation stuff into Spring Security
└── resources
    ├── application-prod.yml                                        # prod overrides
    ├── application-stg.yml                                         # staging overrides
    ├── application.yml                                             # main yaml file

Authentication

Custom Routes

  • GET: /api/auth/validate - Verifies whether the user is authenticated based on cookies stored in the browser.
  • GET: /api/auth/logout - Logs out the user by invalidating the session and removing cookies from the browser. It automatically returns back to the frontend with either:
    • /login?success=false&message=This is the failure message.
    • /login?success=true&message=This is the success message!
    • These should be handled on the frontend route.

Spring OAuth Routes

  • OAuth Initiation: GET: /api/auth/flow/{provider} - Begins the OAuth authentication process for a specific provider. (Example: /api/auth/flow/discord starts the Discord OAuth flow.)
  • OAuth Callback:GET: /api/auth/flow/callback/{provider} - Handles the callback process after the OAuth provider returns data to authenticate the user. (Example: /api/auth/flow/callback/discord processes the callback from Discord OAuth.)
    • If successful, the user is automatically redirected to /dashboard.
    • If failed, the user is automatically redirected to /login?success=false&message=This is the failure message.
      • This should be handled on the frontend route.

Security Details

  • CSRF Protection - Automatically managed by Spring Security, so no additional configuration is required.
  • Auth Validator / Session Token Cookie Setter - Managed by the CustomAuthenticationSuccessHandler.
    • Cookie Settings:
      • Name: session_token
      • Max Age: 30 days (configurable via a private variable).

Backend Objects

Examples

Annotation

You may use the Protected annotation to validate requests manually like so:

@RestController
@RequestMapping("/api")
public class ApiController {

    @PostMapping("/any-user")
    public ResponseEntity<ApiResponder<Empty>> apiAnyUser(@Protected final AuthenticationObject authenticationObject) {
        User user = authenticationObject.getUser(); // guaranteed for user to exist at this line.
    }

    @PostMapping("/admin-only")
    public ResponseEntity<ApiResponder<Empty>> apiForAdmins(@Protected(admin = true) final AuthenticationObject authenticationObject) {
        User user = authenticationObject.getUser(); // guaranteed for an admin user to exist at this line.
    }
}

Imperative

You may use the Protector class to validate requests manually like so:

@RestController
@RequestMapping("/api")
public class ApiController {

    private final Protector protector;

    public AdminController(final Protector protector) {
        this.protector = protector;
    }

    @PostMapping("/any-user")
    public ResponseEntity<ApiResponder<Empty>> apiAnyUser(final HttpServletRequest request) {
        AuthenticationObject authenticationObject = protector.validateSession(request);
        User user = authenticationObject.getUser(); // guaranteed for user to exist at this line.
    }

    @PostMapping("/admin-only")
    public ResponseEntity<ApiResponder<Empty>> apiForAdmins(final HttpServletRequest request) {
        AuthenticationObject authenticationObject = protector.validateAdminSession(request);
        User user = authenticationObject.getUser(); // guaranteed for an admin user to exist at this line.
    }
}

Clients

LeetcodeClient

We have a Leetcode client that interfaces with leetcode.com's GraphQL API to fetch question data, user submissions, and other information. It handles authentication by automatically stealing session cookies via Playwright.

The main client can be found at LeetcodeClientImpl.java, though we only use ThrottledLeetcodeClient.java to avoid rate-limiting.

Client Features

The LeetcodeClient.java interface provides these methods:

  • findQuestionBySlug - Gets full question details including title, difficulty, acceptance rate, and topic tags
  • findSubmissionsByUsername - Fetches recent accepted submissions for a user
  • findSubmissionDetailBySubmissionId - Gets detailed submission info (runtime, memory, code)
  • getPotd - Gets the Problem of the Day
  • getUserProfile - Fetches user profile data (ranking, avatar, bio)
  • getAllTopicTags - Returns all available question topic tags

Warning

This list of methods may not be up to date. Instead, check LeetcodeClient.java

Authentication

We have a scheduled task at LeetcodeAuthStealer.java that:

  • Runs every 30 minutes to refresh session cookies
  • Uses Playwright to automate GitHub OAuth login flow
  • Stores the LEETCODE_SESSION cookie in our database
  • Falls back to email alerts if authentication fails

This is necessary because leetcode.com requires authenticated requests for most API calls.

Rate Limiting

We have a class called ThrottledLeetcodeClient.java (impl) that wraps the main client with rate limiting:

  • Uses bucket4j for token bucket rate limiting
  • Configured for 1 request per 100ms to avoid getting blocked
  • Automatically queues requests when rate limit is hit

GraphQL Queries

All API calls use GraphQL queries stored in the queries package:

  • SelectProblemQuery - Get question details by slug
  • SelectAcceptedSubmisisonsQuery - Get user's accepted submissions
  • GetSubmissionDetails - Get submission runtime/memory details
  • GetPotd - Get daily challenge question
  • GetUserProfile - Get user profile info
  • GetTopics - Get all topic tags

Warning

This list of methods may not be up to date. Instead, check queries/

Error Handling

The client throws RuntimeException for:

  • HTTP status codes other than 200
  • JSON parsing errors
  • Network timeouts
  • Authentication failures

All errors bubble up to the calling services which handle them appropriately.

Usage in Codebase

The client is used throughout the app for:

  • Fetching question data when users submit solutions
  • Getting user profiles for leaderboards
  • Syncing topic tags with our database
  • Calculating scores based on submission details
  • Daily challenge notifications

Reporter

Codebloom has a hand-rolled error reporter that will report certain logs and errors to Discord channels in our private Discord guild.

Implementation Detail

  • Reporter.java is the abstracted reporter class that exposes the following methods:
    • .log is used for non-errors that need to be reported back to the server. We mainly use this for tracking odd behaviors or suspicious activity that may not be an error.
    • .error is used to report any errors back to the server. We use this to raise an indicator to the dev team that something is wrong when it likely shouldn't be. We have logic that will automatically call .error on any controller exceptions or task scheduler (aka any background services).
  • ReporterController.java allows us to ingest errors from our endpoint. The endpoints have basic CSRF protections via checking the Origin header.
    • /api/reporter/error is the endpoint used to ingest errors from the frontend which will be sent to the server.
    • /api/reporter/log is the endpoint used to ingest logs from the frontend which will be sent to the server.
  • ThrottledReporter.java is a rate-limited version of the Reporter class, which exposes the same functions but limits how often they can be called. Used for randomly-selected high-traffic logging.

Examples

Link to Reporter example

    public synchronized String getCsrf() {
        if (csrf == null && !reported) {
            reported = true;
            reporter.log(Report.builder()
                            .environments(env.getActiveProfiles())
                            .location(Location.BACKEND)
                            .data("CSRF token is missing inside of LeetcodeAuthStealer. This may be something to look into.")
                            .build());
        }

        return csrf;
    }

Link to ThrottledReporter example

            throttledReporter.log(Report.builder()
                                            .data(String.format("""
                                                Score Distribution Report

                                                Leetcode Username: %s
                                                Difficulty: %s (%d pts)
                                                Acceptance Rate: %.2f
                                                Question Multiplier: %.2f
                                                Total: %d
                                                """,
                                                user.getLeetcodeUsername(),
                                                leetcodeQuestion.getDifficulty(),
                                                basePoints,
                                                leetcodeQuestion.getAcceptanceRate(),
                                                multiplier,
                                                points
                                                ))
                                            .build());