Trash Low-life Scum
A community hub and show discovery tool for the folk punk scene.
Built to give the folk punk scene a community hub and a way to discover upcoming shows in one place. Much of that scene happens outside mainstream ticketing: house shows, backyard sets, benefit shows, sober-friendly and all-ages nights that never appear on the big platforms. The site is designed around those realities rather than treating every show as a venue and a ticket link. It supports informal and temporary venues, private addresses, and community-maintained listings, alongside the things a scene needs beyond a calendar: band and venue pages, a chat room, direct messages, a mutual aid board, and news.
A Flask application built with the app-factory pattern. Flask-SQLAlchemy and Flask-Migrate handle the data layer, Jinja templates render the pages, and Flask-WTF provides forms with CSRF protection. Write endpoints are rate limited with Flask-Limiter. The core model is bands, venues, and concerts, with a many-to-many link between bands and concerts, plus users, venue notes, notifications, direct messages, mutual aid requests, and news posts. The shows page filters by band, venue, city or state, date range, accessibility, and the all-ages, sober, and benefit flags. It also supports radius search from a postal code, using pgeocode so it works across countries. Login runs through Auth0 (OpenID Connect via Authlib). Roles carried in the token (user, band member, moderator, admin) gate the moderation tools: merging duplicate bands, removing flyers and notes, and warning or messaging users. The community chat is built on Flask-SocketIO, persists every message, and broadcasts live. Flyers are uploaded and stored as images, and user-written bios and posts are written in Markdown. Analytics are privacy-minded: visitors are counted from IP addresses hashed with a daily salt, with country resolved from a local GeoIP database, so raw IP addresses are never stored.
House shows broke the obvious data model. Matching venues by name means every "House Show" or "Backyard" collapses into one giant venue. The fix was an informal-venue flag: those venues are never matched or reused, each submission gets its own row, and they stay out of the public venue directory. That in turn needed a slug generator that guarantees uniqueness by suffixing collisions instead of assuming names are distinct. User-written Markdown is an XSS risk, because the renderer does not sanitize its output. Everything now passes through an allowlist sanitizer (nh3) that permits a small set of formatting tags, only http, https, and mailto links, and forces safe rel attributes on every link. The show search had a subtler bug. Filtering on venue columns before the venue table was joined produced a silent cartesian product whenever it combined with the distance search. Restructuring the query around a single up-front join made the filters independent and the results correct. Finally, the live chat had to fail gracefully. The socket handler checks the session before doing anything, validates and persists each message, and on any database error rolls back and tells the sender their message failed, rather than dropping it silently.