LibreMedia to monolityczna aplikacja Ruby on Rails z wydzielonym API REST dla aplikacji mobilnych i integracji zewnętrznych.
┌─────────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Web Views │ │ API (JSON) │ │ WebSocket │ │
│ │ (Slim/HTML) │ │ (REST v1) │ │ (ActionCable) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Controllers │ │ Policies │ │ Services │ │
│ │ │ │ (Pundit) │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Models │ │ Concerns │ │ Validators │ │
│ │ (ActiveRecord) │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐│
│ │PostgreSQL│ │ Redis │ │Elastics. │ │ Sidekiq │ │ Stripe ││
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘│
└─────────────────────────────────────────────────────────────────┘
Renderowanie po stronie serwera z Hotwire (Turbo + Stimulus)
Layout: landing (strona główna), application (zalogowani użytkownicy)
Responsywny design z SCSS i CSS Grid/Flexbox
Namespace: /api/v1/*
Autentykacja: JWT (Devise + devise-jwt)
Format: JSON
Wersjonowanie: URL prefix (/api/v1/)
Powiadomienia w czasie rzeczywistym
Kanały: NotificationsChannel, CommentsChannel
Web : Dziedziczą po ApplicationController
API : Dziedziczą po Api::V1::BaseController
Namespace Admin:: dla panelu administracyjnego
Autoryzacja na poziomie zasobów
Każdy model ma odpowiadającą policy (np. PostPolicy)
Scopes dla list (policy_scope(Post))
Wydzielona logika biznesowa
Konwencja nazewnictwa: VerbNounService (np. CreateSubscriptionService)
Główne modele:
Model
Opis
User
Użytkownicy systemu
Post
Artykuły tekstowe
Video
Materiały wideo
Photo
Galerie zdjęć
Page
Strony statyczne
Category
Kategorie treści
Tag
Tagi (folksonomia)
Comment
Komentarze
Reaction
Reakcje (like, love, etc.)
Follow
Obserwacje użytkowników
Subscription
Subskrypcje premium
Payment
Historia płatności
Donation
Darowizny
Notification
Powiadomienia
Partner
Partnerzy medialni
Concerns (moduły współdzielone)
Concern
Opis
Używany przez
Sluggable
Automatyczne generowanie slug
Post, Video, Photo, Category, Tag
Translatable
Pola wielojęzyczne (JSONB)
Post, Category, Partner
Publishable
Status publikacji
Post, Video, Photo
Reactable
Reakcje (polimorficzne)
Post, Video, Photo, Comment
Commentable
Komentarze (polimorficzne)
Post, Video, Photo
Taggable
Tagowanie (polimorficzne)
Post, Video, Photo
4. Warstwa infrastruktury
Główna baza danych
UUID jako primary key
JSONB dla danych strukturalnych (tłumaczenia, metadane)
Cache (Rails.cache)
Session store
Sidekiq backend
Action Cable backend
Wyszukiwanie pełnotekstowe
Indeksowane modele: Post, Video, Photo, User
Gem: Searchkick
Przetwarzanie w tle
Kolejki: default, mailers, low, critical
UI: /sidekiq (tylko admin)
Płatności kartowe
Subskrypcje (recurring)
Darowizny (one-time)
Diagram przepływu żądania
┌──────────────┐
│ Client │
│ (Browser/App)│
└──────┬───────┘
│
▼
┌──────────────┐
│ Nginx │
│ (Reverse Pr.)│
└──────┬───────┘
│
▼
┌──────────────┐
│ Puma │
│ (App Server)│
└──────┬───────┘
│
▼
┌──────────────┐
│ Router │
│ (config/routes)
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Middleware │ │ before_ │ │ Pundit │
│ (Rack) │ │ action │ │ authorize │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└──────────────────┼──────────────────┘
│
▼
┌──────────────┐
│ Controller │
│ Action │
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Model │ │ Service │ │ Cache │
│ │ │ │ │ │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└──────────────────┼──────────────────┘
│
▼
┌──────────────┐
│ View │
│ (Slim/JSON) │
└──────┬───────┘
│
▼
┌──────────────┐
│ Response │
└──────────────┘
Autentykacja i autoryzacja
Kanał
Metoda
Gem
Web
Session cookie
Devise
API
JWT Bearer token
devise-jwt
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def show?
record . published? || record . author == user || user &.admin?
end
def update?
record . author == user || user &.admin?
end
class Scope < Scope
def resolve
if user &.admin?
scope . all
else
scope . published . or ( scope . where ( author : user ) )
end
end
end
end
Nagłówki HTTP (secure_headers gem)
SecureHeaders ::Configuration . default do |config |
config . x_frame_options = "DENY"
config . x_content_type_options = "nosniff"
config . x_xss_protection = "1; mode=block"
config . content_security_policy = { ... }
end
Token CSRF dla formularzy
API: wyłączone (stateless JWT)
Rack::Attack dla ochrony przed DDoS/brute-force
Wiele instancji Puma za load balancerem
Sesje w Redis (współdzielone między instancjami)
Sidekiq jako osobne procesy
PostgreSQL: replika read-only
Redis: cluster mode
Elasticsearch: cluster wielowęzłowy