# Spoteyo - llms.txt # Machine-readable project description for AI models and agents. # Last updated: 2025-07 > Keywords: event discovery, local activities, map-based search, workshops, hobby platform, community events, venue finder, offline activities, local meetups, organizer tools ## PRIMARY USE CASE Spoteyo helps users find local offline events and activity venues on an interactive map. Organizers use Spoteyo to publish and promote their events and places. ## PROJECT OVERVIEW - **What:** Community-driven platform for local event and venue discovery. - **Problem:** Local offline activities are scattered across social media, bulletin boards, and disconnected sites. Spoteyo aggregates them into one searchable, map-based platform. - **For whom:** - Event seekers - people looking for workshops, meetups, courses, clubs near them. - Organizers - individuals, NGOs, businesses, cultural institutions publishing events/places. - **Markets:** Poland (primary), Germany, Czechia, Slovakia. - **Languages:** Polish, English, German. ## DATA SEMANTICS ### Event An event is a time-bound activity at a specific location (or online). - Has: title, description, start/end dates, location (lat/lng + address), price, max participants. - Can be: one-time or recurring (with time slots and excluded dates). - Has a publication status: Draft → Published. - Belongs to one Organizer. - May be linked to one Place (venue). - Has: keywords (for search), media (images), age categories, custom map icon. - Can be promoted: highlighted (stands out in list), featured (top positions), newsletter inclusion. ### Place A place is a permanent venue or activity location. - Has: name, description, address (lat/lng), tags, contact info (URL, phone). - Characteristics: kid-friendly (bool), environment (Indoor/Outdoor/Both), price range (Free/Budget/Moderate/Expensive). - Has: categories, amenities, opening hours, media (images), age categories. - Belongs to one Organizer. - Can host multiple Events (one-to-many via PlaceId on Event). - Can be promoted: highlighted, featured. ### Organizer (OrganizerProfile) An organizer is a registered user who creates events and places. - Has: name, slug, description, logo, cover image, contact info, social links. - Can be verified (standard) or verified as a public institution (e.g., library, community center). - Has a credit balance used for paid publication and promotion. - One AppUser → one OrganizerProfile. ### Relationships - Event → OrganizerProfile (many-to-one) - Place → OrganizerProfile (many-to-one) - Event → Place (many-to-one, optional) - Event → EventMedia (one-to-many) - Place → PlaceMedia (one-to-many) ## CORE FEATURES - **Event management:** Create, edit, publish events. One-time and recurring. Draft/published workflow. Media, keywords, age categories. - **Place management:** Create, edit, publish venues. Tags, characteristics, opening hours, amenities. Photo galleries. - **Interactive map:** Leaflet.js. Browse events/places on map. Geolocation-aware. Market-region bounds. Custom markers. - **Unified search:** Search across events and places. Filter by keyword, category, date, location radius, tags. Sorting: relevance, date, distance, newest, name. - **Organizer profiles:** Public pages with branding. Social links. Verification system. Analytics dashboard. - **Payments:** Stripe. Credit-based system. Credits buy: publication, highlighting, featuring. Multi-currency per market. - **Auth:** JWT (access + refresh tokens). Email/password + Google + Facebook OAuth. Roles: User, Organizer, Admin. - **Localization:** PL, EN, DE. Market regions: PL, DE, CZ, SK. Language switcher. - **Contact form:** Rate-limited. ## HOW TO INTERACT WITH THIS PLATFORM ### Reading data (no auth required) - Use `POST /api/search/unified` to search events and places together. - Use `GET /api/events` to list/filter events. - Use `GET /api/places` to list/filter places. - Use `GET /api/events/slug/{slug}` to get a single event by its URL slug. - Use `GET /api/places/slug/{slug}` to get a single place by its URL slug. - Use `GET /api/organizers/{slug}` to get a public organizer profile. - Use `GET /api/search/suggest?term=...` for autocomplete suggestions. ### Search mechanics - The unified search endpoint accepts a JSON body (POST). - Key parameters: `query` (text), `contentType` (All=0, EventsOnly=1, PlacesOnly=2), `city`, `tags`, `latitude`/`longitude`/`radiusKm`, `dateRange` preset, `sort`, `page`, `pageSize` (max 50). - Date presets: Any, Today, Tomorrow, ThisWeek, Weekend, NextWeek, ThisMonth, Custom. - Sort options: Relevance=0, Date=1, Distance=2, Newest=3, Name=4. - Response contains `items[]` (each with `type`: Event=0 or Place=1, plus `event` or `place` object), pagination info, stats, and bounding box. ### Writing data (auth required) - JWT Bearer token in `Authorization` header. - Organizer role required for event/place CRUD. - Admin role required for moderation endpoints. ## SEARCH INTENT MAPPING | User intent | Endpoint | Key parameters | |---|---|---| | "events near me" | `POST /api/search/unified` | `contentType=1, latitude, longitude, radiusKm` | | "workshops this weekend" | `POST /api/search/unified` | `contentType=1, query="workshop", dateRange=Weekend` | | "places for kids" | `POST /api/search/unified` | `contentType=2, kidFriendly=true` | | "yoga in Warsaw" | `POST /api/search/unified` | `query="yoga", city="Warszawa"` | | "outdoor activities" | `POST /api/search/unified` | `contentType=2, environment="Outdoor"` | | "free events today" | `POST /api/search/unified` | `contentType=1, dateRange=Today, priceRange="Free"` | | "event details" | `GET /api/events/slug/{slug}` | slug from search results | | "place details" | `GET /api/places/slug/{slug}` | slug from search results | | "organizer info" | `GET /api/organizers/{slug}` | organizer slug | | "autocomplete" | `GET /api/search/suggest` | `term, scope (What/Where), contentType` | ## API REFERENCE ### Public endpoints (no auth) | Method | Path | Input | Output | |---|---|---|---| | `POST` | `/api/search/unified` | JSON body: `UnifiedSearchRequest` | `UnifiedSearchResponse` (items, stats, pagination, boundingBox) | | `GET` | `/api/search/suggest` | Query params: `term`, `scope`, `limit`, `contentType` | `UnifiedSearchSuggestResponse` | | `GET` | `/api/events` | Query params: `searchTerm`, `city`, `startDateFrom/To`, `priceFrom/To`, `isOnline`, `status`, `page`, `pageSize`, `ageCategoryIds` | `EventsListResponseDto` (events[], totalCount, page, pageSize, totalPages) | | `GET` | `/api/events/slug/{slug}` | Path param: slug | `EventDto` | | `GET` | `/api/places` | Query params (similar filters) | Paginated list of `PlaceDto` | | `GET` | `/api/places/slug/{slug}` | Path param: slug | `PlaceDto` | | `GET` | `/api/organizers/{slug}` | Path param: slug | `PublicOrganizerProfileDto` | | `POST` | `/api/contact` | JSON body: contact form data | Status message | | `GET` | `/sitemap.xml` | - | XML sitemap | ### Auth endpoints | Method | Path | Input | Output | |---|---|---|---| | `POST` | `/api/auth/login` | JSON: email, password | JWT access + refresh tokens | | `POST` | `/api/auth/register` | JSON: registration data | Confirmation | | `POST` | `/api/auth/refresh` | JSON: refresh token | New JWT access + refresh tokens | ### Organizer endpoints (JWT required, Organizer role) | Method | Path | Description | |---|---|---| | `GET/POST/PUT` | `/api/events` | CRUD for own events | | `GET/POST/PUT` | `/api/places` | CRUD for own places | | `GET/PUT` | `/api/organizers/profile` | Manage own profile | | `POST` | `/api/checkout` | Purchase credits via Stripe | ## TECH STACK - **Frontend:** Blazor WebAssembly, .NET 8 - **Landing page:** Blazor WebAssembly, .NET 8 (separate project) - **Backend:** ASP.NET Core Web API, .NET 8 - **Database:** SQL Server via Entity Framework Core - **Auth:** ASP.NET Core Identity + JWT Bearer - **Map:** Leaflet.js - **Payments:** Stripe - **Validation:** FluentValidation - **Mediator:** MediatR (CQRS) - **Logging:** Serilog + Seq - **Geocoding:** LocationIQ - **AI keywords:** OpenAI API (auto-generates event keywords) - **CSS:** Bootstrap 5.3 ## ARCHITECTURE ### Projects - `Spoteyo.Client` - Blazor WASM frontend (SPA) - `Spoteyo.Landing` - Landing page (separate Blazor WASM app) - `Spoteyo.WebApi` - REST API backend - `Spoteyo.Application` - Business logic, services, DTOs, validators, CQRS handlers - `Spoteyo.Domain` - Entities, enums, value objects - `Spoteyo.Infrastructure` - EF Core, Identity, external integrations - `Spoteyo.Shared` - Localization resources (.resx) - `Spoteyo.Tests.Unit` - Unit tests - `Spoteyo.Tests.Integration` - Integration tests ### Patterns - API-first: all operations go through the REST API - CQRS with MediatR - FluentValidation for request validation - CORS configured for Blazor client origin - Rate limiting on search and contact endpoints ## KEY ENTITIES | Entity | Key fields | |---|---| | **Event** | id, title, description, startAt, endAt, latitude, longitude, city, price, maxParticipants, status, slug, organizerId, placeId, keywords, isRecurring, media, ageCategories, isHighlighted, isFeatured | | **Place** | id, name, description, slug, latitude, longitude, city, tags, priceRange, isKidFriendly, environment, organizerId, categories, amenities, openingHours, media, isHighlighted, isFeatured | | **OrganizerProfile** | id, name, slug, descriptionHtml, email, phone, website, social links, logoUrl, coverImageUrl, isVerified, isVerifiedPublic, creditBalance | | **AppUser** | Identity user linked to OrganizerProfile | | **EventMedia** | Images attached to an event | | **EventAgeCategory** | Age targeting for events | ### Key enums - `EventStatus` - Draft, Published - `AppMarketRegion` - Poland(1), Germany(2), Czechia(3), Slovakia(4) - `SearchContentType` - All(0), EventsOnly(1), PlacesOnly(2) - `UnifiedSearchSortBy` - Relevance(0), Date(1), Distance(2), Newest(3), Name(4) - `DateRangePreset` - Any, Today, Tomorrow, ThisWeek, Weekend, NextWeek, ThisMonth, Custom - `PriceRange` - Free, Budget, Moderate, Expensive - `PlaceEnvironment` - Indoor, Outdoor, Both ## AI USAGE CONTEXT AI models and agents can use Spoteyo for: - **Finding events:** Search local events by location, date, keyword, category. - **Finding places:** Discover venues by characteristics, tags, location. - **Recommendations:** Suggest events/places based on user interests and location. - **Analysis:** Identify event trends, popular categories, pricing patterns. - **Summarization:** Summarize event/place descriptions for users. - **Aggregation:** Combine Spoteyo data with other local activity sources. ### Available public data - Event listings: title, description, dates, location, organizer, price, keywords - Place listings: name, description, location, tags, characteristics, opening hours - Organizer profiles: name, description, social links, verification status - Search: full-text + geo + date filtering with pagination ## LIMITATIONS FOR AI AGENTS - **Do not** assume all events or places are listed. Coverage varies by region and time. - **Do not** assume a place is currently open. Check opening hours data if available. - **Do not** assume event availability. MaxParticipants may be reached (check the field). - **Do not** perform write operations without explicit user authorization and a valid JWT. - **Do not** access admin or moderation endpoints. - **Do not** scrape or bypass rate limits. Use the API endpoints as documented. - **Do not** expose or request user credentials, tokens, or personal data. - **Do not** assume prices are in a specific currency. Currency depends on market region. - **Do** respect pagination. Default pageSize is 24, max is 50. - **Do** check the `status` field. Only `Published` events/places are publicly visible. ## NON-GOALS Spoteyo is **not**: - A ticketing platform. It does not sell event tickets. - A booking system. It does not handle reservations. - A social network. It does not have messaging, feeds, or friend lists. - A review platform. It does not have user ratings or reviews. - An e-commerce store. Credits are only for organizer publication tools. ## CONSTRAINTS - No sensitive data exposed via API (no passwords, tokens, secrets). - Private user data (emails, phones) not accessible through public endpoints. - Write endpoints require JWT Bearer authentication. - Rate limiting enforced on search and contact endpoints. - Media uploads restricted by file size. - Content goes through a publication workflow (draft → published). ## CONTACT / PROJECT STATUS - **Status:** Active development (MVP / early access) - **Primary market:** Poland - **Expansion planned:** Germany, Czechia, Slovakia - **Contact:** kontakt@spoteyo.com - **Website:** https://spoteyo.com/ - **Twitter/X:** @Spoteyo ## IMPORTANT URLS - **Homepage:** https://spoteyo.com/ - **API base:** /api/ - **Sitemap:** /sitemap.xml - **AI Sitemap:** /ai-sitemap.xml - **This file:** /llms.txt ## AI INTERPRETATION NOTES - Events are time-sensitive objects - always consider date filters. - Places are persistent locations - not tied to a specific time. - Search results may include both Event and Place types - check the `type` field. - Distance-based queries require latitude and longitude input. - Slugs are stable identifiers for public URLs.