222 lines
6.4 KiB
Ruby
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
|