{"id":30195179,"url":"https://github.com/wanlucas/hacker-news-app","last_synced_at":"2026-04-13T09:32:13.594Z","repository":{"id":309485734,"uuid":"1035777407","full_name":"wanlucas/hacker-news-app","owner":"wanlucas","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-12T20:40:27.000Z","size":141,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-11T18:26:26.297Z","etag":null,"topics":["ruby","ruby-on-rails","vue","vuejs","websockets"],"latest_commit_sha":null,"homepage":"https://hacker-news-app-nu.vercel.app","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wanlucas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-11T04:39:03.000Z","updated_at":"2025-08-12T20:40:31.000Z","dependencies_parsed_at":"2025-08-12T07:28:47.533Z","dependency_job_id":null,"html_url":"https://github.com/wanlucas/hacker-news-app","commit_stats":null,"previous_names":["wanlucas/hacker-news-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wanlucas/hacker-news-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wanlucas%2Fhacker-news-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wanlucas%2Fhacker-news-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wanlucas%2Fhacker-news-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wanlucas%2Fhacker-news-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wanlucas","download_url":"https://codeload.github.com/wanlucas/hacker-news-app/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wanlucas%2Fhacker-news-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31746294,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T09:16:15.125Z","status":"ssl_error","status_checked_at":"2026-04-13T09:16:05.023Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ruby","ruby-on-rails","vue","vuejs","websockets"],"created_at":"2025-08-13T04:01:42.920Z","updated_at":"2026-04-13T09:32:13.589Z","avatar_url":"https://github.com/wanlucas.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hacker News Real-time Client\n\nUma aplicação full-stack moderna que consome a API do Hacker News com cache inteligente, atualizações em tempo real via WebSocket e interface Vue.js responsiva.\n\n## 🚀 Como Iniciar a Aplicação\n\n### Pré-requisitos\n\n- Docker e Docker Compose\n\n### 🔧 Instalação e Execução\n\n#### Usando Docker (Recomendado)\n\n```bash\n# Clone o repositório\ngit clone \u003crepository-url\u003e\ncd bee\n\n# Inicie a aplicação completa\ndocker-compose up\n\n# Para rebuild após mudanças\ndocker-compose up --build\n```\n\n**URLs de Acesso:**\n- Frontend (Vue.js): http://localhost:5173\n- Backend API (Rails): http://localhost:3000\n- API Endpoints: http://localhost:3000/api/stories\n\n#### Execução Local (Desenvolvimento)\n\n**Backend (Rails):**\n```bash\ncd server\n\nbundle install\nrails server -p 3000\n```\n\n**Frontend (Vue.js):**\n```bash\n\nnpm install\nnpm run dev\n```\n\n### 🌍 Variáveis de Ambiente\n\n#### Frontend (Client)\n\n- VITE_SOCKET_URL=ws://localhost:3000/cable\n- VITE_API_URL=http://localhost:3000\n- VITE_DEBUG=true\n\n\n#### Backend (Server)\n\n- CLIENT_URL=http://localhost:5173\n\n## 🏗️ Arquitetura da Aplicação\n\n### 📋 Visão Geral\n\nA aplicação segue uma arquitetura limpa com separação clara de responsabilidades, implementando patterns de design sólidos e práticas modernas de desenvolvimento.\n\n```\n┌─────────────────┐    WebSocket     ┌──────────────────┐\n│   Vue.js Client │◄──────────────────┤  Rails API       │\n│                 │                   │                  │\n│ • Components    │    HTTP API       │ • Controllers    │\n│ • Composables   │◄──────────────────┤ • Services       │\n│ • Services      │                   │ • Jobs           │\n└─────────────────┘                   │ • Channels       │\n                                      └──────────────────┘\n                                               │\n                                      ┌──────────────────┐\n                                      │  Hacker News API │\n                                      │                  │\n                                      │ • Stories        │\n                                      │ • Comments       │\n                                      │ • Items          │\n                                      └──────────────────┘\n```\n\n### 🎯 Backend (Rails API)\n\n#### 🔗 Injeção de Dependências\n\nA aplicação utiliza um **Service Factory** para gerenciar dependências de forma limpa:\n\n```ruby\n# app/services/service_factory.rb\nclass ServiceFactory\n  def self.hacker_news_service\n    HackerNewsService.new(\n      http_client: HttpClient.new('https://hacker-news.firebaseio.com/v0'),\n      cache_repository: Repositories::Cache::Rails.new,\n      broadcasting_service: BroadcastingService.new,\n      logger: Rails.logger\n    )\n  end\nend\n```\n\n**Vantagens:**\n- ✅ Baixo acoplamento entre componentes\n- ✅ Fácil substituição de dependências para testes\n- ✅ Configuração centralizada\n- ✅ Facilita mocking em testes unitários\n\n#### 🎨 Repository Pattern\n\nImplementação do padrão Repository para abstração do cache:\n\n```ruby\n# Abstração base\nclass Repositories::Cache::Base\n  def read(key); end\n  def write(key, value, options = {}); end\n  def exist?(key); end\n  def delete(key); end\nend\n\n# Implementação Rails\nclass Repositories::Cache::Rails \u003c Base\n  # Implementação usando Rails.cache\nend\n```\n\n**Benefícios:**\n- ✅ Abstração da camada de persistência\n- ✅ Facilita troca de engines de cache\n- ✅ Testabilidade melhorada\n- ✅ Separação de responsabilidades\n\n#### 💾 Sistema de Cache Inteligente\n\nA aplicação implementa uma estratégia de cache com **Stale-While-Revalidate**:\n\n```ruby\n# app/lib/cached_api.rb\nclass CachedApi\n  def load_cache(key, revalidate_fn:)\n    data = @cache_repository.read(key)\n\n    # Retorna cache válido imediatamente\n    return data if cache_is_valid?(key)\n\n    if data.nil?\n      # Cache miss - busca dados fresh\n      data = revalidate_fn.call\n    elsif !@cache_repository.exist?(\"#{key}_lock\")\n      # Cache stale - atualiza em background\n      background_refresh(key, revalidate_fn)\n    end\n\n    data\n  end\nend\n```\n\n**Características:**\n- 🚀 **Performance**: Retorna dados em cache instantaneamente\n- 🔄 **Freshness**: Atualiza dados em background quando stale\n- 🔒 **Concorrência**: Lock mechanism previne múltiplas atualizações\n- ⚡ **Graceful Degradation**: Continua servindo cache stale se API falhar\n\n#### 🔄 Jobs Assíncronos e Worker Periódico\n\n**Job de Atualização:**\n```ruby\n# app/jobs/stories_update_job.rb\nclass StoriesUpdateJob \u003c ApplicationJob\n  def perform\n    ServiceFactory.hacker_news_service.update_cache\n  end\nend\n```\n\n**Worker Periódico:**\n```ruby\n# config/initializers/periodic_jobs.rb\nThread.new do\n  loop do\n    StoriesUpdateJob.perform_now\n    sleep 5.minutes\n  end\nend\n```\n\n**Benefícios:**\n- ⏰ Atualizações automáticas a cada 5 minutos\n- 🔄 Mantém cache sempre atualizado\n- 📊 Broadcast de novos dados via WebSocket\n\n#### 🌐 HTTP Client Robusto\n\n```ruby\n# app/services/http_client.rb\nclass HttpClient\n  def get(endpoint)\n    # Implementação com tratamento de erros\n    # Logging detalhado\n    # Parsing JSON automático\n  end\nend\n```\n\n**Features:**\n- 🔍 Logging detalhado de requests\n- ⚠️ Error handling customizado\n- 🔄 Retry logic implícito\n- 📊 Debugging tools integrados\n\n#### ⚡ Paralelização Inteligente\n\nO serviço utiliza **threading** para otimizar fetching de dados:\n\n```ruby\n# Busca stories em paralelo (batches de 10)\nids.each_slice(10) do |batch|\n  threads = batch.map do |id|\n    Thread.new { fetch_story(id) }\n  end\n  threads.each(\u0026:join)\nend\n\n# Busca comments em paralelo (batches de 30)\nids.each_slice(30) do |batch|\n  threads = batch.map do |id|\n    Thread.new { fetch_comment(id) }\n  end\nend\n```\n\n**Vantagens:**\n- 🚀 Redução significativa de latência\n- 📊 Throughput otimizado\n- 🔒 Thread-safe com Mutex\n- ⚖️ Load balancing automático\n\n#### 📡 WebSocket Real-time\n\n```ruby\n# app/channels/stories_channel.rb\nclass StoriesChannel \u003c ActionCable::Channel::Base\n  def subscribed\n    stream_from \"stories_updates\"\n  end\n\n  def self.broadcast_new_stories(stories)\n    ActionCable.server.broadcast('stories_updates', {\n      type: 'new_stories',\n      data: stories\n    })\n  end\nend\n```\n\n## 🧪 Testes Automatizados\n\n### 📋 Estrutura de Testes\n\n```ruby\n\n# test/services/hacker_news_service_test.rb\nclass HackerNewsServiceTest \u003c ActiveSupport::TestCase\n  def setup\n    @http_client = mock_http_client\n    @cache_repository = mock_cache_repository\n    @broadcasting_service = mock_broadcasting_service\n    @logger = mock_logger\n  end\n\n  test \"should return stories from cache when valid\" do\n    # Testa comportamento de cache válido\n  end\n\n  test \"should fetch fresh data on cache miss\" do\n    # Testa cache miss scenario\n  end\nend\n```\n\n**Cobertura de Testes:**\n- ✅ **Unit Tests**: Serviços, repositories, jobs\n- ✅ **Integration Tests**: APIs, controllers\n- ✅ **Mock Objects**: Isolamento de dependências\n- ✅ **Edge Cases**: Error handling, timeouts\n- ✅ **Performance Tests**: Caching behavior\n\n### 🔧 Executar Testes\n\n```bash\n# Todos os testes\ncd server\nrails test\n\n# Testes específicos\nrails test test/services/hacker_news_service_test.rb\n\n# Com cobertura\nrails test:coverage\n```\n\n## 🔧 Tecnologias Utilizadas\n\n### Backend\n- **Ruby on Rails 8.0** - Framework web\n- **Action Cable** - WebSocket real-time\n- **Puma** - Application server\n- **Net::HTTP** - HTTP client nativo\n- **Rails Cache** - Sistema de cache\n- **dotenv-rails** - Gerenciamento de variáveis de ambiente\n\n### Frontend  \n- **Vue.js 3** - Framework reativo\n- **Vite** - Build tool e dev server\n- **Axios** - HTTP client\n- **Action Cable JS** - WebSocket client\n- **DOMPurify** - Sanitização XSS\n\n\n## 📝 API Endpoints\n\n```\nGET /api/stories          # Lista top stories\nGET /api/stories/search   # Busca stories por query\nPOST /api/stories/update  # Force cache update\nWS /cable                 # WebSocket connection\n```\n\n\n**Desenvolvido com 🎵 usando Ruby on Rails e Vue.js**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwanlucas%2Fhacker-news-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwanlucas%2Fhacker-news-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwanlucas%2Fhacker-news-app/lists"}