04

From scattered spreadsheetsto a structuredrole-based workflow

Company
Graduation Project
Year
2025 – 2026
Type
Full-stack System · Role Workflow
Role
Solo Designer & Developer

Universities manage academic competitions through spreadsheets, group chats, and manual processes. This graduation project delivered a complete B/S system with role-based workflows for administrators, teachers, and students — covering the full lifecycle from competition publishing to result archiving.

Competition management system — admin dashboard

A graduation project that had to work like a real product

A campus with 8,000+ students, 12 departments, and 100+ academic competitions every year — all managed through WeChat groups and Excel files. My graduation brief was to replace that entire workflow with a structured system.

I was the sole designer and developer. No team to delegate to — every decision from database schema to button placement was mine to make and defend in front of the graduation committee.

Competition info lived in group chats and spreadsheets nobody maintained

Before this system, the typical workflow looked like this: an administrator posts competition details in a WeChat group, students ask follow-up questions that get buried in chat history, teachers collect registrations through shared Excel files, and results end up in different folders on different computers every semester.

01

No single source of truth

Competition details, deadlines, and registration status scattered across WeChat messages, email attachments, and multiple Excel versions. Students miss deadlines because they can't find the latest info.

02

Manual review bottleneck

Teachers review registrations one by one in spreadsheets with no status tracking. Peak periods (100+ registrations in a week) create delays and lost submissions.

03

No historical data

Past competition results aren't archived systematically. When the university needs participation statistics for accreditation reports, staff spend days collecting data from old files.

Why front-end/back-end separation and role-based access

The core insight was that competition management isn't one workflow — it's three different workflows sharing the same data. An administrator publishes and configures events. A teacher reviews registrations and manages results. A student browses, registers, and checks outcomes. Each role needs a different view of the same underlying system.

This drove two key architecture decisions: (1) separate the frontend from the backend so each role gets a tailored interface while sharing one API and database, and (2) implement RBAC at the API layer so permissions are enforced server-side, not just hidden in the UI.

Before writing any code, I mapped the three role workflows on paper: what does an admin do day-to-day? What does a teacher need during peak registration week? What does a student care about? This exercise revealed that 70% of the data model could be derived from those three journey maps — the remaining 30% was supporting infrastructure (auth, notifications, audit logs).

“The project forced me to think beyond individual pages. Each module had to fit the same role model, permission rules, and lifecycle state logic so the system would feel coherent end to end.”

Admin dashboard showing system overview

Designing for three users who see the same data differently

The central design challenge wasn't technical — it was information architecture. The same competition record means different things to different roles: an admin sees configuration options, a teacher sees a review queue, a student sees a registration card. I had to design one data model that serves three mental models.

01

Permission as UX

Rather than showing everything and graying out forbidden actions, each role gets a tailored dashboard. A student never sees admin controls — not because they're hidden, but because the interface is genuinely different.

02

Lifecycle as navigation

Competition state (Draft → Published → Registration → Review → Results → Archived) drives what actions are available. Instead of a static sidebar, the UI surfaces contextual actions based on where a competition is in its lifecycle.

03

Batch over one-by-one

Teachers told me their biggest pain was reviewing registrations one at a time. I designed the review interface around batch actions: select multiple, approve/reject with one click, add notes in bulk. This single design choice addressed the #2 problem directly.

04

Data as institutional memory

Every registration, review decision, and result is timestamped and attributed. The system becomes the university's competition archive automatically — no extra 'archiving' step needed. This solved problem #3 as a side effect of normal operation.

Competition plaza with events

Trade-offs I made and why they worked

JWT + Spring Security + Redis

Stateless tokens for scalability, with Redis-backed token blacklisting for logout. This let me avoid session state on the server while still supporting forced logout for compromised accounts.

15-table normalized schema

Designed to support complex queries: 'all registrations for competition X, grouped by team, with review status and reviewer info.' Denormalization was tempting but would have made the review pipeline much harder to maintain.

JMeter load testing at 100 concurrency

Simulated peak registration periods. Discovered that the competition list query was N+1-ing on category joins — fixed with eager loading, bringing P95 from 1.2s to 180ms.

Vue 3 Composition API + Element Plus

Chose Composition API over Options API for better logic reuse across role-specific pages. Built shared composables for table pagination, form validation, and permission-aware button rendering.

User management interface

A working system and a successful defense

To validate the system under realistic conditions, I seeded it with data modeled on our university's actual competition schedule — 110 events across a full academic year, with 2,134 registration records spanning individual and team entries. This let me stress-test every workflow path and demonstrate the system's capacity during my defense.

Full lifecycle coverage

110 seeded competitions exercised every state transition: draft, publish, open registration, review, result publication, and archive. Each path was demonstrated live.

Concurrency validated

JMeter tests at 100 concurrent users confirmed the system handles peak registration periods. The optimization from N+1 fix (P95: 1.2s → 180ms) was validated against the full dataset.

Defense: live demo, passed

Demonstrated all three role workflows live. The panel highlighted the RBAC design and the system's ability to handle the full competition lifecycle without manual intervention.

Full admin dashboard with charts

What I learned and what I'd do differently

01

End-to-end ownership builds confidence

Owning every layer — from database schema to API design to frontend components — taught me how decisions ripple across a system. When I changed the permission model, I felt it in every layer.

02

Start with user flows, not tech stack

If I started over, I'd map the three role workflows first and derive the data model from those — instead of designing the database schema up front and fitting the UI around it.

03

Testing should come earlier

JMeter caught real performance issues, but only at the end. Next time I'd write integration tests alongside implementation and catch N+1 queries before they compound.

Next Project

Hermes + Obsidian Wiki