Building a Platform to Streamline Archiving and Member Management
Team Cook · Chungbuk National University
github.com/openWEBsw/project-soyongdori
Hello everyone. Before we get into the slides, we'd like to start with a short pre-recorded demo video so you can get a feel for the actual product — what it looks like, how it flows, and what it can do — before we explain how it was built. Please take a look.
Alright, now that you've seen the product in action, let's walk through the slides and explain the decisions behind it.
Our project is called SYDR — short for Soyongdori. Soyongdori is the central band club here at Chungbuk National University, and SYDR is the web platform we built for it.
Before this project, the club ran almost entirely on manual processes. Announcements went out over KakaoTalk, event schedules lived in personal calendar apps, and documents were scattered across Google Drive with no consistent structure. There was no single place to find anything.
Membership management was just as fragmented. Joining the club meant filling out a form somewhere and waiting for someone to manually approve it. No one could easily see who had what role, and when leadership changed every semester, there was no structured handover — the next president just got a spreadsheet and hoped for the best.
Access permissions were also unclear — in practice anyone who had the Google Drive link could see everything, which is a real security risk. And club accounting meant someone manually photographing receipts and typing amounts into a spreadsheet by hand. Our goal was to replace all of that with one systematic, always-accessible web platform: proper permission controls, durable archiving, and a clear workflow for every process the club relies on.
Before we get into the individual features, here's the overall structure of the site. There are about thirteen screens, organized by permission tier. A visitor starts as a guest, signs up to become a temporary member, submits a membership application, gets approved by an admin, and finally becomes a full member — and what each screen shows and allows follows that progression. You can think of this map as the blueprint that ties together all the features I'm about to walk through.
A quick rundown of what we used. The whole project is written in TypeScript — a typed superset of JavaScript — on both the frontend and the backend. On the frontend: React, Tailwind CSS, and FullCalendar.js. On the backend: Node.js with Express, as a stateless REST API. The database is MySQL with Prisma as the ORM. And for infrastructure, we deploy with Docker Compose on Oracle Cloud, with Cloudflare handling the tunnel and the CDN.
Our first key feature is the permission system. We implemented a Role-Based Access Control model using JWT — JSON Web Tokens — for stateless authentication. When a user logs in, the server issues an access token and a refresh token. The access token carries the user's permission level encoded inside it. The server never needs to look up a session in the database on every request, which keeps the API fast and horizontally scalable.
For granularity, we defined nine permission levels — zero through eight — that map to the club's actual job-title hierarchy. A brand new applicant has level zero, a regular member might be level two or three, and the club president is at the top. On the backend, we built a custom Express middleware using a helper called requireLevel, which wraps any route you want to protect. If the token's level doesn't meet the threshold, the request is rejected right at the middleware layer — before any business logic runs.
Concretely, permissions control: who can write or only read on the bulletin board, who can access the admin dashboard, whether a user can create and edit calendar events or just view them, and who can see personal member information. This directly solved the access-control problem the club had before — where visibility into club data was basically all-or-nothing.
The second feature is the membership application workflow. We designed it as a four-step pipeline. In step one, a prospective member signs up on the site: a user record is created, their status is set to "pending," and their basic info is saved to the MEMBERS table.
In step two, they fill out an application form — instrument, cohort, motivations, that kind of thing. That data gets inserted into a separate JOIN_APPLICATIONS table, linked to the user record via a foreign key.
Step three: the admin opens the dashboard and sees a list of pending applications. There's an approve or reject button for each one. One click is all it takes — no spreadsheets, no KakaoTalk messages back and forth.
And step four is where everything resolves atomically. Approval is processed inside a single database transaction: the user's status flips from "pending" to "active," and their permission level is automatically granted based on their declared job title. Because it's transactional, there's no state where a user is approved but still has no permissions, or vice versa. Data integrity is guaranteed.
Third feature: the user interface itself. One of the most visible parts of the site is the schedule management system, built on FullCalendar.js. We support three separate calendar layers — a personal calendar, a member-visible calendar, and a public official calendar — all rendered in real-time. Users can create, edit, and delete events depending on their permission level.
Responsive design was a core priority. The same codebase runs on a wide desktop monitor and on a 375-pixel-wide phone screen without any separate mobile site. We used Tailwind's responsive utilities and fluid typography with CSS clamp() so every layout adapts seamlessly — which matters because club members check the schedule on their phones far more than on a desktop.
The fourth feature was probably the most exciting one to build. On the club accounting board, a treasurer can upload a photo of a receipt. The system runs it through an OCR pipeline that extracts the date, individual items, quantities, and amounts — automatically. No more manual transcription.
Under the hood, we're using the Google Gemini 3.1 Flash Lite model via API. We chose it because it's lightweight and fast — response times are low enough to feel interactive — and cost-effective for a student project with real usage. The key engineering work was on the system prompt: getting the model to return clean, structured JSON output reliably took careful prompt engineering. But once we had it dialed in, the results were surprisingly accurate even with handwritten receipt images.
For collaboration, we used a structured Git branching strategy. Each new feature lived on its own feature branch. We integrated on a shared dev branch where we could run tests and catch cross-feature issues. When things were stable, we merged into main via pull requests. And releases got their own tagged release branches so we always had a clean production-ready snapshot.
Every merge went through a pull request review. Team members read each other's code and left comments before anything landed on main. One person primarily owned merge responsibility and handled final bug checks before promoting to main or a release branch. This kept our main branch stable throughout the semester — we had a working, deployable version at all times.
Now I want to talk about the real challenges we hit — the things that didn't work the first time and what we did to fix them. These were some of the most valuable learning experiences of the project.
FullCalendar.js generates its own internal DOM structure, and you can't easily target those elements with standard CSS selectors. We needed to customize the look of the calendar to match our design system, but the library's internals were essentially a black box. We solved it by carefully studying the library's rendered HTML, mapping out the class names it generates, and using Tailwind's arbitrary-variant selectors to target them directly. It was tedious but it worked.
Timezone handling. Everything worked perfectly in local development, but after we deployed to the server, saved event times would drift by nine hours — sometimes forward, sometimes backward — depending on which direction the offset was applied. The root cause was a mismatch: the server operated in UTC, the database stored times in UTC, but the client was sending times in Korean Standard Time — KST, which is UTC plus nine. And in certain code paths, the offset was being applied twice. We fixed it by establishing a single conversion convention: UTC everywhere on the server and database side, KST conversion only at the presentation layer, applied exactly once.
So where are we now? We have a fully deployed, production-grade TypeScript web platform that replaces every manual process the club was relying on. The deployment is live on Oracle Cloud behind Cloudflare. Real club members are using it for testing right now, and we're collecting feedback for the next iteration.
Looking ahead, we plan to continue improving the platform based on real usage — refining the admin experience, expanding the AI receipt analysis, and potentially adding push notifications for event reminders. The foundation is solid, and the architecture is set up to support these extensions without a rewrite.
Being honest about what's left, one of the first things on our list is how much of the site is still hardcoded. Right now the home page's hero images, the part photos on the introduction page, and even highlights like our next stage and rehearsal times are baked into the code at build time — there is literally a "TODO" comment in our home page reminding us to wire these up to the API. The next step is to move that content into the database and give club officers a small admin panel, so changing a poster or an announcement doesn't require a developer and a redeploy.
// TODO: API 연동 주석을 솔직하게 언급. 핵심 메시지: 운영진이 코드 수정 없이 직접 콘텐츠를 바꿀 수 있게 만들 것.
On the infrastructure side, uploaded files like profile pictures are currently stored as local file paths on the server, and high-resolution uploads can degrade — both already flagged as TODOs in our schema and code. We plan to move file storage to object storage such as MinIO or Cloudflare R2, fix the high-resolution handling, and replace the last few magic numbers — like the club's founding year and the default cohort — with values driven by configuration and the database. We've also already laid the groundwork in our schema for archiving the boards year by year, which ties directly back to our original goal: durable, organized record-keeping that survives every leadership handover.
Now let's switch over to the live site. I'll walk through a few key flows so you can see everything working in real-time.
First, the sign-up and membership approval flow. I'll create a new account — you can see the form validates in real-time. After sign-up, the account is in "pending" status. Now I'll fill out the membership application form. Let's switch to the admin dashboard — here's the application sitting in the queue. I'll approve it, assign a job title, and you'll see the status flip to "active" instantly, with permissions granted automatically.
Next, the calendar. I'll create a new event — notice that the interface is fully responsive. Let me resize the browser window down to phone width, or better yet, here's the same page on my phone. The calendar adjusts automatically — no horizontal scroll, everything fits. This is the concrete outcome of the responsive work we described earlier.
Finally, the AI receipt analysis. I'll navigate to the accounting board and upload a receipt image. Watch what happens — within a couple of seconds the system parses the receipt and populates the date, items, and total amount automatically. No typing required. This is the Gemini API at work behind the scenes.
That's everything for our presentation. To summarize: we built SYDR — a full-stack TypeScript web platform for a real university band club — covering RBAC authentication, automated membership workflows, a responsive interactive calendar, and AI-assisted accounting. The site is live, it's responsive enough to use on the phone you're holding right now, and it's already in the hands of real users.
The source code is public at github.com/openWEBsw/project-soyongdori. We'd love for you to take a look, and if you want to check the responsive design claim for yourself, you can open the live site on your phone right now. Thank you very much for listening — we're happy to take any questions.