Portrait of Patrice GADEGBEPatrice GADEGBE

Soccercup

Soccercup is an application for managing amateur football tournaments, in which an organiser composes the groups, schedules the matches then enters the goals and the cards, the server taking care of recomputing the scores and the standings.

Role
Author of the mobile application and main author of the API, to which a second developer contributed
Period
From June 2025 to January 2026, for the most part in June and July 2025
Status
Prototype. Working prototype, unpublished
Technologies
Java 21, Spring Boot 3.5, PostgreSQL 16, Flutter, flutter_bloc
Repositories
Two
Architecture of SoccercupA Flutter application calls, in JSON, a Spring Boot API, which stores its data in PostgreSQL and the uploaded images on disk.ApplicationFlutter, flutter_bloc14 cubits, 11 routesAPISpring Boot 3.5, Java 2135 endpointsPostgreSQL 16schema managed by HibernateJSON, multipartJPADiskuploaded images
Figure 1. Architecture of Soccercup. The application receives nothing from the server apart from its own requests.

Context

Soccercup is aimed at the organiser of an amateur tournament: a cup is defined in it by its number of groups, its number of knockout rounds and its points scale, after which the teams are distributed among the groups and the matches scheduled.

The application was first developed on its own, from the end of June 2025, against simulated data. The API was written afterwards, in about ten days, from 13 to 23 July 2025.

Problem

In a tournament, an entry is never final. A goal is credited to the wrong team, a card is withdrawn after discussion, and each of these corrections must show up at once in the score of the match, in the team’s row within its group and in the rank it holds there. Keeping these three values by hand, or changing each of them separately, runs the risk that a displayed standing no longer matches the matches played.

A cup also chains two phases of a different nature, the groups then the elimination rounds, which the application must present in five distinct views without the server having to know those views.

Answer

An event log as the only input

The organiser enters neither a score nor a standing. They record match events, that is a goal, a yellow card or a red card, and the server recomputes the whole cup after each creation, modification or deletion of an event: it recounts the events of each match, rebuilds the row of each team in its group, then re-ranks each group. A corrected goal therefore cannot leave a stale score or table behind it.

An explicit tie-break

The ranking of a group follows an order written in the code rather than left to the organiser’s judgement: points, then goal difference, goals scored, the fewest red cards and finally the fewest yellow cards. The points scale belongs to the cup, with three points per win, one per draw and none per defeat unless another choice is made, and only the group matches already started are counted.

teamGroups.sort((tg1, tg2) -> {
    // Compare by points (descending)
    int cmp = Integer.compare(tg2.getPoints(), tg1.getPoints());
    if (cmp != 0)
        return cmp;
    // Compare by goal difference (scored - conceded, descending)
    int tg1GoalDiff = tg1.getScoredGoal() - tg1.getConcededGoal();
    int tg2GoalDiff = tg2.getScoredGoal() - tg2.getConcededGoal();
    cmp = Integer.compare(tg2GoalDiff, tg1GoalDiff);
    if (cmp != 0)
        return cmp;
    // Compare by scored goals (descending)
    cmp = Integer.compare(tg2.getScoredGoal(), tg1.getScoredGoal());
    if (cmp != 0)
        return cmp;
    // Compare by red cards (ascending, fewer is better)
    cmp = Integer.compare(tg1.getRedCards(), tg2.getRedCards());
    if (cmp != 0)
        return cmp;
    // Compare by yellow cards (ascending, fewer is better)
    cmp = Integer.compare(tg1.getYellowCards(), tg2.getYellowCards());
    return cmp;
});
Listing 1. Tie-break order within a group, in the service that recomputes the cup.

A single table for both phases

The group matches and those of the knockout stage share one table, where three columns indicate the phase, the matchday and the position in the bracket. The application asks only for one list of matches per cup, which it splits itself into results and schedule of the groups, and into bracket, results and schedule of the knockout stage. The cup, for its part, goes through four states, from preparation to closure, and the knockout stage disappears from the display when a cup provides for none.

Limits and outlook

The API is not protected by any authentication, so that any client can modify any cup. That choice was enough for a prototype and, as things stand, rules out open use. A player joining a team by an invitation code is moreover simulated in the application: the server does produce that code, without yet offering the endpoint that would allow it to be used.

Recomputing the whole cup on every event is simple to reason about, but it costs one query per match and per counter and does not run in a transaction; it will have to be made incremental if a cup exceeds a few dozen matches. Lastly, the refreshing of the screens relies on an event bus internal to the application, so that a second phone sees a change only on its next load.

The tests come down to a startup test on the server side, and the application has none. The database schema is managed by Hibernate without a migration tool, which will have to change before it is put into real service.

Back to the other projects