fic_tracker/lib/fic_tracker/server.rb

222 lines
6.4 KiB
Ruby

# frozen_string_literal: true
require 'sinatra/base'
module FicTracker
# Web server for providing your fic tracking needs
class Server < Sinatra::Base
def initialize(*)
@task_runner = Thread.new { background_tasks }
super
end
configure :development do
require 'sinatra/reloader'
register Sinatra::Reloader
end
configure do
root = File.join(__dir__, '../..')
set :views, File.join(root, 'views')
set :public_folder, File.join(root, 'public')
enable :logging
end
helpers do
def backend
return env[:ft_backend] if env[:ft_backend]
return halt 400, 'No backend provided' unless params['backend']
env[:ft_backend] = Backends.get params['backend']
return halt 400, "Unable to find backend #{params['backend']}" unless env[:ft_backend]
env[:ft_backend]
end
end
before do
expires 3600, :public, :must_revalidate if request.request_method == 'GET'
end
get '/', provides: :html do
haml :index, format: :html5
end
get '/search/:backend', provides: :html do |_backend_name|
search = backend.get_search_info
haml :search, format: :html5, locals: { search:, backend: }
end
get '/search/:backend', provides: :json do |_backend_name|
search = backend.get_search_info
if params['tag'] || params['name']
raise "Missing params, must specify both tag and name" unless params['tag'] && params['name']
return search.class.find_tags(params['tag'], params['name']).to_json
end
search.to_info_json
end
post '/search/:backend', provides: :html do |_backend_name|
request.body.rewind # in case someone already read it
data = JSON.parse request.body.read, symbolize_names: true
results = backend.sarch(data)
haml :search_result, format: :html5, locals: { results:, backend: }
end
post '/search/:backend', provides: :json do |_backend_name|
request.body.rewind # in case someone already read it
data = JSON.parse request.body.read, symbolize_names: true
results = backend.sarch(data)
results.to_json
end
get '/author/:backend/:slug', provides: :html do |_backend_name, slug|
author = Models::Author.find(backend_name: backend.name, slug:)
author ||= Models::Author.new(backend_name: backend.name, slug:)
author.refresh_metadata
haml :author, format: :html5, locals: { author:, backend: }
ensure
author&.save_changes
end
get '/collection/:backend/:slug', provides: :html do |_backend_name, slug|
collection = Models::Collection.find(backend_name: backend.name, slug:)
collection ||= Models::Collection.new(backend_name: backend.name, slug:)
collection.refresh_metadata
haml :collection, format: :html5, locals: { collection:, backend: }
ensure
collection&.save_changes
end
head '/story/:backend/*.*' do |_backend_name, slug, format|
mime = nil
case format
when 'epub', :epub
format = :epub
mime = 'application/epub+zip'
when 'html', :html
format = :html
mime = 'text/html'
when 'txt', :txt, 'md', :md
format = :markdown
mime = 'text/markdown'
else
halt 400, "Unknown format #{format}"
end
content_type mime
attachment "#{story.safe_name}.#{format}"
story = Models::Story.find(backend_name: backend.name, slug:)
if story
story.set(last_accessed: Time.now)
last_modified story.updated_at || story.published_at
etag story.etag
end
ensure
story&.save_changes
end
# rubocop:disable Metrics/BlockLength
get '/story/:backend/*.*' do |_backend_name, slug, format|
mime = nil
case format
when 'epub', :epub
format = :epub
mime = 'application/epub+zip'
when 'html', :html
format = :html
mime = 'text/html'
when 'txt', :txt, 'md', :md
format = :markdown
mime = 'text/markdown'
else
halt 400, "Unknown format #{format}"
end
story = Models::Story.find(backend_name: backend.name, slug:)
story ||= Models::Story.new(backend_name: backend.name, slug:)
story.ensure_fully_loaded unless story.chapters&.any?
story.set(last_accessed: Time.now)
content_type mime
attachment "#{story.safe_name}.#{format}"
last_modified story.updated_at || story.published_at
etag story.etag
story.ensure_fully_loaded
FicTracker::Renderers.render(format, story)
ensure
story&.save_changes
end
# rubocop:enable Metrics/BlockLength
get '/story/:backend/:slug', provides: :html do |_backend_name, slug|
story = Models::Story.find(backend_name: backend.name, slug:)
story ||= Models::Story.new(backend_name: backend.name, slug:)
story.ensure_fully_loaded
story.set(last_accessed: Time.now)
last_modified story.updated_at || story.published_at
etag story.etag
haml :story, format: :html5, locals: { story:, backend: }
ensure
story&.save_changes
end
get '/story/:backend/:slug/:index', provides: :html do |_backend_name, slug, index|
story = Models::Story.find(backend_name: backend.name, slug:)
story ||= Models::Story.new(backend_name: backend.name, slug:)
story.ensure_fully_loaded
story.set(last_accessed: Time.now)
chapter = story.chapters[index.to_i]
last_modified chapter.published_at
etag chapter.etag
haml :chapter, format: :html5, locals: { chapter:, story:, backend: }
ensure
story&.save_changes
end
private
def background_tasks
$stderr.puts "Starting background task loop"
loop do
FicTracker::Models::Story.expire
FicTracker::Models::Author.expire
FicTracker::Models::Story.needing_content_refresh.each(&:refresh_content)
FicTracker::Models::Story.needing_metadata_refresh.each(&:refresh_metadata)
FicTracker::Models::Author.needing_metadata_refresh.each(&:refresh_metadata)
FicTracker.cache.expire
rescue StandardError => e
FicTracker.logger.error "Failed when running background tasks, #{e.class}: #{e}\n#{e.backtrace[-5,5].join("\n ")}"
ensure
iter += 1
sleep 30 * 60
end
rescue StandardError => e
$stderr.puts "Fatal background error: #{e.class}: #{e}"
end
end
end