mirror of
https://github.com/Neetpone/foalfetch.git
synced 2025-03-10 21:50:07 +01:00
initial: something resembling a minimum viable product
This commit is contained in:
commit
563aa6a0f8
130 changed files with 6276 additions and 0 deletions
7
.gitattributes
vendored
Normal file
7
.gitattributes
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# See https://git-scm.com/docs/gitattributes for more about git attribute files.
|
||||
|
||||
# Mark the database schema as having been generated.
|
||||
db/schema.rb linguist-generated
|
||||
|
||||
# Mark any vendored files as having been vendored.
|
||||
vendor/* linguist-vendored
|
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
/tmp/*
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
|
||||
# Ignore pidfiles, but keep the directory.
|
||||
/tmp/pids/*
|
||||
!/tmp/pids/
|
||||
!/tmp/pids/.keep
|
||||
|
||||
|
||||
/public/assets
|
||||
|
||||
# Ignore master key for decrypting credentials and more.
|
||||
/config/master.key
|
||||
|
||||
public/cached-images
|
||||
|
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
@ -0,0 +1 @@
|
|||
ruby-3.2.2
|
41
Gemfile
Normal file
41
Gemfile
Normal file
|
@ -0,0 +1,41 @@
|
|||
source "https://rubygems.org"
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
ruby "3.2.2"
|
||||
|
||||
gem "rails", "~> 7.0.8", ">= 7.0.8.1"
|
||||
gem "sprockets-rails"
|
||||
gem "pg", "~> 1.1"
|
||||
gem "puma", "~> 5.0"
|
||||
gem 'slim-rails'
|
||||
gem 'kaminari'
|
||||
|
||||
gem 'redis'
|
||||
|
||||
gem 'elasticsearch-model'
|
||||
gem 'model-msearch'
|
||||
gem 'fancy_searchable', github: 'Twibooru/fancy_searchable', ref: '40687c9'
|
||||
|
||||
gem 'sidekiq'
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
|
||||
|
||||
# Use Sass to process CSS
|
||||
# gem "sassc-rails"
|
||||
|
||||
group :development, :test do
|
||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||
gem "debug", platforms: %i[ mri mingw x64_mingw ]
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem "web-console"
|
||||
gem 'annotate'
|
||||
end
|
||||
|
||||
group :test do
|
||||
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
||||
gem "capybara"
|
||||
gem "selenium-webdriver"
|
||||
end
|
291
Gemfile.lock
Normal file
291
Gemfile.lock
Normal file
|
@ -0,0 +1,291 @@
|
|||
GIT
|
||||
remote: https://github.com/Twibooru/fancy_searchable.git
|
||||
revision: 40687c924d19a51335a79f5157150aca9d531cc9
|
||||
ref: 40687c9
|
||||
specs:
|
||||
fancy_searchable (0.2.5)
|
||||
activesupport (>= 7.0)
|
||||
elasticsearch-model (>= 7.2.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord (7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activestorage (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.8.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
base64 (0.2.0)
|
||||
bindex (0.8.1)
|
||||
builder (3.2.4)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.11)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
crass (1.0.6)
|
||||
date (3.3.4)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
elasticsearch (7.17.10)
|
||||
elasticsearch-api (= 7.17.10)
|
||||
elasticsearch-transport (= 7.17.10)
|
||||
elasticsearch-api (7.17.10)
|
||||
multi_json
|
||||
elasticsearch-model (7.2.1)
|
||||
activesupport (> 3)
|
||||
elasticsearch (~> 7)
|
||||
hashie
|
||||
elasticsearch-transport (7.17.10)
|
||||
faraday (>= 1, < 3)
|
||||
multi_json
|
||||
erubi (1.12.0)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
hashie (5.0.0)
|
||||
i18n (1.14.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.7.2)
|
||||
irb (1.12.0)
|
||||
rdoc
|
||||
reline (>= 0.4.2)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
kaminari-activerecord (= 1.2.2)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-actionview (1.2.2)
|
||||
actionview
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-activerecord (1.2.2)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
loofah (2.22.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.22.3)
|
||||
model-msearch (0.0.2)
|
||||
elasticsearch-model
|
||||
multi_json (1.15.0)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
net-imap (0.4.10)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.3-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
pg (1.5.6)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.4)
|
||||
puma (5.6.8)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.9)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.8.1)
|
||||
actioncable (= 7.0.8.1)
|
||||
actionmailbox (= 7.0.8.1)
|
||||
actionmailer (= 7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actiontext (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.8.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
railties (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rake (13.1.0)
|
||||
rdoc (6.6.3.1)
|
||||
psych (>= 4.0.0)
|
||||
redis (5.1.0)
|
||||
redis-client (>= 0.17.0)
|
||||
redis-client (0.21.1)
|
||||
connection_pool
|
||||
regexp_parser (2.9.0)
|
||||
reline (0.5.0)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.2.6)
|
||||
rubyzip (2.3.2)
|
||||
selenium-webdriver (4.19.0)
|
||||
base64 (~> 0.2)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
sidekiq (7.2.2)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.19.0)
|
||||
slim (5.2.1)
|
||||
temple (~> 0.10.0)
|
||||
tilt (>= 2.1.0)
|
||||
slim-rails (3.6.3)
|
||||
actionpack (>= 3.1)
|
||||
railties (>= 3.1)
|
||||
slim (>= 3.0, < 6.0, != 5.0.0)
|
||||
sprockets (4.2.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
stringio (3.1.0)
|
||||
temple (0.10.3)
|
||||
thor (1.3.1)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.1)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uri (0.13.0)
|
||||
web-console (4.2.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
websocket (1.2.10)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.13)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
capybara
|
||||
debug
|
||||
elasticsearch-model
|
||||
fancy_searchable!
|
||||
kaminari
|
||||
model-msearch
|
||||
pg (~> 1.1)
|
||||
puma (~> 5.0)
|
||||
rails (~> 7.0.8, >= 7.0.8.1)
|
||||
redis
|
||||
selenium-webdriver
|
||||
sidekiq
|
||||
slim-rails
|
||||
sprockets-rails
|
||||
tzinfo-data
|
||||
web-console
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.2p53
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.10
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2024 Neetpone
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# FoalFetch
|
||||
A replacement for FiMFetch.
|
6
Rakefile
Normal file
6
Rakefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require_relative "config/application"
|
||||
|
||||
Rails.application.load_tasks
|
6
app/assets/config/manifest.js
Normal file
6
app/assets/config/manifest.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
//= link_tree ../images
|
||||
//= link_directory ../stylesheets .css
|
||||
//= link_tree ../../javascript .js
|
||||
//= link_tree ../../../vendor/javascript .js
|
||||
|
||||
//= link application.js
|
0
app/assets/images/.keep
Normal file
0
app/assets/images/.keep
Normal file
98
app/assets/javascripts/application.js
Normal file
98
app/assets/javascripts/application.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
const makeEl = function(html) {
|
||||
const template = document.createElement('template');
|
||||
|
||||
template.innerHTML = html.trim();
|
||||
|
||||
return template.content.firstChild;
|
||||
};
|
||||
|
||||
function addTag(element, tag) {
|
||||
const existingElements = element.value.split(',').filter(n => n);
|
||||
|
||||
if (existingElements.indexOf(tag) === -1) {
|
||||
existingElements.push(tag);
|
||||
element.value = existingElements.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag(element, tag) {
|
||||
const existingElements = element.value.split(',').filter(n => n);
|
||||
const index = existingElements.indexOf(tag);
|
||||
|
||||
if (index !== -1) {
|
||||
existingElements.splice(index, 1);
|
||||
element.value = existingElements.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
function makeTagElement(hiddenInput, visualInput, tagName, excluded) {
|
||||
// Set up the tag element
|
||||
const tagEl = makeEl(`<div class="${excluded ? 'excluded' : ''}"><span>${tagName}</span></div>`);
|
||||
tagEl.firstChild.addEventListener('click', function(e) {
|
||||
if (tagEl.classList.contains('excluded')) {
|
||||
removeTag(hiddenInput, '-' + tagName);
|
||||
addTag(hiddenInput, tagName);
|
||||
tagEl.classList.remove('excluded');
|
||||
} else {
|
||||
removeTag(hiddenInput, tagName);
|
||||
addTag(hiddenInput, '-' + tagName);
|
||||
tagEl.classList.add('excluded');
|
||||
}
|
||||
});
|
||||
|
||||
// Set up the child "X" button
|
||||
const removeEl = makeEl('<a class="remove" />');
|
||||
removeEl.addEventListener('click', function(e) {
|
||||
removeTag(hiddenInput, tagEl.classList.contains('excluded') ? '-' + tagName : tagName);
|
||||
visualInput.removeChild(tagEl);
|
||||
});
|
||||
|
||||
tagEl.appendChild(removeEl);
|
||||
|
||||
return tagEl;
|
||||
}
|
||||
|
||||
function setupFancyTagEditor(rootElement) {
|
||||
rootElement.classList.remove('hidden');
|
||||
|
||||
const selectDropdown = rootElement.querySelector('select');
|
||||
const hiddenInput = rootElement.querySelector('input[type=text]');
|
||||
const visualInput = rootElement.querySelector('.selected-tags');
|
||||
|
||||
if (!selectDropdown || !hiddenInput || !visualInput) {
|
||||
console.log('Nothing there!');
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenInput.setAttribute('type', 'hidden');
|
||||
|
||||
// Load existing tags from the input field as visual tag elements
|
||||
for (const tag of hiddenInput.value.split(',').filter(n => n)) {
|
||||
let tagElement;
|
||||
if (tag[0] === '-') {
|
||||
tagElement = makeTagElement(hiddenInput, visualInput, tag.substring(1), true);
|
||||
} else {
|
||||
tagElement = makeTagElement(hiddenInput, visualInput, tag, false);
|
||||
}
|
||||
|
||||
visualInput.appendChild(tagElement);
|
||||
}
|
||||
|
||||
selectDropdown.addEventListener('change', function(e) {
|
||||
const tagName = selectDropdown.value;
|
||||
addTag(hiddenInput, tagName);
|
||||
visualInput.appendChild(makeTagElement(hiddenInput, visualInput, tagName, false));
|
||||
});
|
||||
}
|
||||
|
||||
function whenReady() {
|
||||
for (const element of document.querySelectorAll('.js-tag-editor')) {
|
||||
setupFancyTagEditor(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
whenReady();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', whenReady);
|
||||
}
|
151
app/assets/stylesheets/about.css
Normal file
151
app/assets/stylesheets/about.css
Normal file
|
@ -0,0 +1,151 @@
|
|||
body.about {
|
||||
background-color: #fff6ea;
|
||||
}
|
||||
|
||||
.about article {
|
||||
display: inline-block;
|
||||
max-width: 800px;
|
||||
text-align: left;
|
||||
padding: 0 5px;
|
||||
line-height: 20px;
|
||||
font-size: 18px;
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
.about article h1, .about article h2 {
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
margin: 5px;
|
||||
}
|
||||
.about article h1 {
|
||||
font-size: 35px;
|
||||
margin: 15px 0 25px;
|
||||
}
|
||||
|
||||
.about article section {
|
||||
padding: 10px 15px;
|
||||
background-color: #fdfdfd;
|
||||
margin: 0 0 15px;
|
||||
overflow: auto;
|
||||
box-shadow: 0 0 5px #a5a5a5 , 0 0 8px #797979;
|
||||
}
|
||||
|
||||
.about p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.about dl {
|
||||
line-height: 24px;
|
||||
}
|
||||
.about dt {
|
||||
float: left;
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
font-family: Montserrat, Arial, sans-serif;
|
||||
color: #187099;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
.about dd {
|
||||
margin: 0 0 0 110px;
|
||||
color: #0F5221;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.about dd:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
.about code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.about pre {
|
||||
background-color: #eff0f1;
|
||||
}
|
||||
.about img.fb-logo {
|
||||
margin: 15px 0 0 15px;
|
||||
max-width: 64px;
|
||||
}
|
||||
.about .tile {
|
||||
width: 150px;
|
||||
height: 180px;
|
||||
font-size: 10px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
/*border: 1px solid #333;*/
|
||||
}
|
||||
.about .tile img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.about .tiles {
|
||||
text-align: center;
|
||||
}
|
||||
.about .tile figure {
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
height: 180px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.about .tile figcaption {
|
||||
min-height: 45px;
|
||||
}
|
||||
.about .tile figcaption span {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.collapsable {
|
||||
overflow: hidden;
|
||||
-webkit-transition-delay: 2s;
|
||||
transition-delay: 2s;
|
||||
transition: max-height .5s ease-out;
|
||||
}
|
||||
.collapsable.full {
|
||||
transition: max-height .8s ease-in;
|
||||
max-height:0px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.about .c {
|
||||
text-align: center;
|
||||
}
|
||||
.about .stars {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 60px;
|
||||
}
|
||||
.about .stars .star {
|
||||
font-size: 45px;
|
||||
line-height: 45px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin: 5px 4px;
|
||||
}
|
||||
.about .stars .star:before {
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.about article .stat {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.donates h3 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
.donates ul.names {
|
||||
list-style: none;
|
||||
}
|
||||
.donates ul.names li {
|
||||
height: 35px;
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
margin: 3px 4px;
|
||||
}
|
50
app/assets/stylesheets/application.css
Normal file
50
app/assets/stylesheets/application.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
|
||||
* vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
*/
|
||||
.cols {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.opts {
|
||||
text-align: left;
|
||||
margin: 0.75rem;
|
||||
}
|
||||
.opts > input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.opts > label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fake-link {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input#show-advanced + div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input#show-advanced:checked + div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-s > form {
|
||||
text-align: left;
|
||||
padding-left: 12rem;
|
||||
}
|
3548
app/assets/stylesheets/fimfetch.css
Normal file
3548
app/assets/stylesheets/fimfetch.css
Normal file
File diff suppressed because it is too large
Load diff
4
app/channels/application_cable/channel.rb
Normal file
4
app/channels/application_cable/channel.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
4
app/channels/application_cable/connection.rb
Normal file
4
app/channels/application_cable/connection.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
21
app/controllers/application_controller.rb
Normal file
21
app/controllers/application_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
before_action :start_timer
|
||||
before_action :setup_pagination_and_tags
|
||||
|
||||
private
|
||||
|
||||
def start_timer
|
||||
@start_time = Time.zone.now
|
||||
end
|
||||
|
||||
def setup_pagination_and_tags
|
||||
@per_page = 15
|
||||
per_page = params[:per_page]
|
||||
if per_page
|
||||
per_page = per_page.to_i
|
||||
@per_page = per_page if per_page.between? 1, 50
|
||||
end
|
||||
|
||||
@page_num = params[:page].to_i
|
||||
end
|
||||
end
|
5
app/controllers/authors_controller.rb
Normal file
5
app/controllers/authors_controller.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AuthorsController < ApplicationController
|
||||
def show
|
||||
@author = Author.find(params[:id])
|
||||
end
|
||||
end
|
6
app/controllers/chapters_controller.rb
Normal file
6
app/controllers/chapters_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class ChaptersController < ApplicationController
|
||||
def show
|
||||
@story = Story.find(params[:story_id])
|
||||
@chapter = @story.chapters.find_by(number: params[:id])
|
||||
end
|
||||
end
|
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
28
app/controllers/images_controller.rb
Normal file
28
app/controllers/images_controller.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
require 'open-uri'
|
||||
|
||||
class ImagesController < ApplicationController
|
||||
def show
|
||||
url = params[:url]
|
||||
parsed = URI.parse(url)
|
||||
|
||||
if parsed.host != 'cdn-img.fimfiction.net'
|
||||
render nothing: true, status: :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
hash = Digest::SHA256.hexdigest(url)
|
||||
path = Rails.root.join('public', 'cached-images', hash + File.extname(url))
|
||||
our_url = '/cached-images/' + hash + File.extname(url)
|
||||
|
||||
if File.exist? path
|
||||
redirect_to our_url
|
||||
return
|
||||
end
|
||||
|
||||
File.open(path, 'wb') do |fp|
|
||||
fp.write(Net::HTTP.get(parsed))
|
||||
end
|
||||
|
||||
redirect_to our_url
|
||||
end
|
||||
end
|
125
app/controllers/search_controller.rb
Normal file
125
app/controllers/search_controller.rb
Normal file
|
@ -0,0 +1,125 @@
|
|||
# This whole class is a giant mess but I coded it fast so give me a break.
|
||||
class SearchController < ApplicationController
|
||||
ALLOWED_SORT_DIRS = [:asc, :desc]
|
||||
ALLOWED_SORT_FIELDS = [:title, :author, :date_published, :date_updated, :num_words, :rel]
|
||||
|
||||
before_action :load_tags
|
||||
|
||||
def index
|
||||
@search_params = {}
|
||||
end
|
||||
|
||||
def search
|
||||
unless setup_scope
|
||||
return
|
||||
end
|
||||
|
||||
using_random = @search_params['luck'].present?
|
||||
|
||||
# This was mainly written this way to match the way FiMFetch's query interface looks, without using JS.
|
||||
# I should do a Derpibooru-esque textual search system sometime.
|
||||
@search = Story.fancy_search(per_page: @per_page,
|
||||
page: @page_num) do |s|
|
||||
s.add_query match: { title: { query: @search_params['q'], operator: 'AND' } } unless @search_params['q'].blank?
|
||||
s.add_query match: { author: { query: @search_params['author'], operator: 'AND' } } if @search_params['author'].present?
|
||||
|
||||
# ratings -> match stories with any of them
|
||||
s.add_filter bool: {
|
||||
should: @search_params['ratings'].keys.map { |k| { term: { content_rating: k } } }
|
||||
} unless @search_params['ratings'].blank?
|
||||
|
||||
# completeness -> match stories with any of them
|
||||
s.add_filter bool: {
|
||||
should: @search_params['state'].keys.map { |k| { term: { completion_status: k } } }
|
||||
} unless @search_params['state'].blank?
|
||||
|
||||
# tags -> match any of the included tags, exclude any of the excluded taags
|
||||
tag_musts, tag_must_nots = parse_tag_queries
|
||||
|
||||
s.add_filter terms: {
|
||||
tags: tag_musts
|
||||
} if tag_musts.any?
|
||||
|
||||
s.add_filter bool: {
|
||||
must_not: tag_must_nots.map { |t| { term: { tags: t } } }
|
||||
} if tag_must_nots.any?
|
||||
|
||||
# sort direction
|
||||
if using_random
|
||||
s.add_sort _random: :desc
|
||||
else
|
||||
s.add_sort parse_sort
|
||||
end
|
||||
end
|
||||
|
||||
if using_random && @search.total_count > 0
|
||||
redirect_to story_path(@search.records[0])
|
||||
return
|
||||
end
|
||||
|
||||
@records = @search.records
|
||||
end
|
||||
|
||||
private
|
||||
def load_tags
|
||||
@character_tags = Tag.where(type: 'character').pluck(:name)
|
||||
@other_tags = Tag.where.not(type: 'character').pluck(:name)
|
||||
end
|
||||
|
||||
# returns: [included tags, excluded tags]
|
||||
def parse_tag_queries
|
||||
tag_searches = (@search_params['tags'] + ',' + @search_params['characters']).split(',').reject &:blank?
|
||||
|
||||
[tag_searches.select { |t| t[0] != '-' }, tag_searches.select { |t| t[0] == '-' }]
|
||||
end
|
||||
|
||||
def parse_sort
|
||||
sf = ALLOWED_SORT_FIELDS.detect { |f| @search_params['sf'] == f.to_s } || :date_updated
|
||||
sd = ALLOWED_SORT_DIRS.detect { |d| @search_params['sd'] == d.to_s } || :desc
|
||||
|
||||
sf = case sf
|
||||
when :rel then
|
||||
:_score
|
||||
else
|
||||
sf
|
||||
end
|
||||
|
||||
{sf => sd}
|
||||
end
|
||||
|
||||
# FIXME: This is some of the worst Ruby code I have ever written.
|
||||
def setup_scope
|
||||
@scope_key = Random.hex(16)
|
||||
scope_valid = false
|
||||
|
||||
# scope passed, try to look it up in redis and use the search params from it
|
||||
if params[:scope].present?
|
||||
result = $redis.get("search_scope/#{params[:scope]}")
|
||||
if result.present?
|
||||
@search_params = JSON.load(result)
|
||||
@scope_key = params[:scope]
|
||||
scope_valid = true
|
||||
else
|
||||
redirect_to '/'
|
||||
return false
|
||||
end
|
||||
else
|
||||
@search_params = params
|
||||
end
|
||||
|
||||
# you can't JSON.dump a Parameters
|
||||
if @search_params.is_a? ActionController::Parameters
|
||||
@search_params = @search_params.permit!.to_h
|
||||
end
|
||||
$redis.setex("search_scope/#{@scope_key}", 3600, JSON.dump(@search_params))
|
||||
|
||||
# if the scope was invalid (no key passed, or invalid key passed), redirect to the results page
|
||||
# with the new key we just generated for the params we had.
|
||||
unless scope_valid
|
||||
redirect_to "/search?scope=#{@scope_key}"
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
4
app/controllers/static_pages_controller.rb
Normal file
4
app/controllers/static_pages_controller.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class StaticPagesController < ApplicationController
|
||||
def about
|
||||
end
|
||||
end
|
9
app/controllers/stories_controller.rb
Normal file
9
app/controllers/stories_controller.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class StoriesController < ApplicationController
|
||||
def index
|
||||
end
|
||||
|
||||
def show
|
||||
@story = Story.find(params[:id])
|
||||
@chapters = @story.chapters.order(number: :asc)
|
||||
end
|
||||
end
|
15
app/helpers/application_helper.rb
Normal file
15
app/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
module ApplicationHelper
|
||||
# https://infusion.media/content-marketing/how-to-calculate-reading-time/
|
||||
def reading_time(words)
|
||||
minutes = words / 200.0
|
||||
|
||||
distance_of_time_in_words(minutes * 60.0)
|
||||
end
|
||||
|
||||
def render_time
|
||||
diff = ((Time.zone.now - @start_time) * 1000.0).round(2)
|
||||
diff < 1000 ? "#{diff}ms" : "#{(diff / 1000.0).round(2)}s"
|
||||
rescue StandardError
|
||||
'unknown ms'
|
||||
end
|
||||
end
|
2
app/helpers/authors_helper.rb
Normal file
2
app/helpers/authors_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module AuthorsHelper
|
||||
end
|
2
app/helpers/chapters_helper.rb
Normal file
2
app/helpers/chapters_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module ChaptersHelper
|
||||
end
|
2
app/helpers/images_helper.rb
Normal file
2
app/helpers/images_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module ImagesHelper
|
||||
end
|
47
app/helpers/search_helper.rb
Normal file
47
app/helpers/search_helper.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module SearchHelper
|
||||
def rating_display(rating)
|
||||
case rating
|
||||
when 'everyone' then
|
||||
tag.div class: 'frating ev', title: 'Rated Everyone' do
|
||||
'Everyone'
|
||||
end
|
||||
when 'teen' then
|
||||
tag.div class: 'frating', title: 'Rated Teen' do
|
||||
'Teen'
|
||||
end
|
||||
when 'explicit' then
|
||||
tag.div class: 'frating', title: 'Rated Explicit' do
|
||||
'Explicit'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def status_display(status)
|
||||
case status
|
||||
when 'complete' then
|
||||
tag.div class: 'fstatus sc', title: 'Complete' do
|
||||
'Complete'
|
||||
end
|
||||
when 'incomplete' then
|
||||
tag.div class: 'fstatus si', title: 'Incomplete' do
|
||||
'Incomplete'
|
||||
end
|
||||
when 'cancelled' then
|
||||
tag.div class: 'fstatus sn', title: 'Cancelled' do
|
||||
'Cancelled'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tag_to_html(t)
|
||||
tag.div class: 'ftag', title: t.name do
|
||||
t.name
|
||||
end
|
||||
end
|
||||
|
||||
def character_tag(t)
|
||||
tag.div class: 'ftag', title: t.name do
|
||||
t.name
|
||||
end
|
||||
end
|
||||
end
|
2
app/helpers/static_pages_helper.rb
Normal file
2
app/helpers/static_pages_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module StaticPagesHelper
|
||||
end
|
2
app/helpers/stories_helper.rb
Normal file
2
app/helpers/stories_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module StoriesHelper
|
||||
end
|
61
app/indexes/story_index.rb
Normal file
61
app/indexes/story_index.rb
Normal file
|
@ -0,0 +1,61 @@
|
|||
module StoryIndex
|
||||
def self.included(base)
|
||||
base.settings index: { number_of_shards: 5, max_result_window: 10_000_000 } do
|
||||
mappings dynamic: false do
|
||||
indexes :id, type: 'integer'
|
||||
indexes :author_id, type: 'keyword'
|
||||
indexes :completion_status, type: 'keyword'
|
||||
indexes :content_rating, type: 'keyword'
|
||||
indexes :date_published, type: 'date'
|
||||
indexes :date_updated, type: 'date'
|
||||
indexes :date_modified, type: 'date'
|
||||
indexes :num_comments, type: 'integer'
|
||||
indexes :num_views, type: 'integer'
|
||||
indexes :num_words, type: 'integer'
|
||||
indexes :rating, type: 'integer'
|
||||
indexes :short_description, type: 'text', analyzer: 'snowball'
|
||||
indexes :description_html, type: 'text', analyzer: 'snowball'
|
||||
indexes :title, type: 'text', analyzer: 'snowball'
|
||||
indexes :author, type: 'text', analyzer: 'snowball'
|
||||
indexes :tags, type: 'keyword'
|
||||
end
|
||||
end
|
||||
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def default_sort(_options = {})
|
||||
[date_published: :desc]
|
||||
end
|
||||
|
||||
def allowed_search_fields(access_options = {})
|
||||
[:title, :completion_status, :content_rating, :date_published, :date_updated, :date_modified, :num_comments, :num_views, :num_words, :rating, :short_description, :description_html, :title, :tags, :author]
|
||||
end
|
||||
end
|
||||
|
||||
def as_json(*)
|
||||
{
|
||||
id: id,
|
||||
author_id: author.id,
|
||||
completion_status: completion_status,
|
||||
content_rating: content_rating,
|
||||
date_published: date_published,
|
||||
date_updated: date_updated,
|
||||
date_modified: date_modified,
|
||||
num_comments: num_comments,
|
||||
num_views: num_views,
|
||||
num_words: num_words,
|
||||
rating: rating,
|
||||
short_description: short_description,
|
||||
description_html: description_html,
|
||||
title: title,
|
||||
tags: tags.map(&:name),
|
||||
author: author.name
|
||||
}
|
||||
end
|
||||
|
||||
def as_indexed_json(*)
|
||||
as_json
|
||||
end
|
||||
end
|
7
app/jobs/application_job.rb
Normal file
7
app/jobs/application_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class ApplicationJob < ActiveJob::Base
|
||||
# Automatically retry jobs that encountered a deadlock
|
||||
# retry_on ActiveRecord::Deadlocked
|
||||
|
||||
# Most jobs are safe to ignore if the underlying records are no longer available
|
||||
# discard_on ActiveJob::DeserializationError
|
||||
end
|
11
app/jobs/index_update_job.rb
Normal file
11
app/jobs/index_update_job.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexUpdateJob < ApplicationJob
|
||||
queue_as :high
|
||||
def perform(cls, id)
|
||||
obj = cls.constantize.find(id)
|
||||
obj.update_index(defer: false) if obj
|
||||
rescue StandardError => ex
|
||||
Rails.logger.error ex.message
|
||||
end
|
||||
end
|
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class ApplicationRecord < ActiveRecord::Base
|
||||
primary_abstract_class
|
||||
end
|
3
app/models/author.rb
Normal file
3
app/models/author.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Author < ApplicationRecord
|
||||
has_many :stories
|
||||
end
|
3
app/models/chapter.rb
Normal file
3
app/models/chapter.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Chapter < ApplicationRecord
|
||||
belongs_to :story
|
||||
end
|
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
34
app/models/concerns/indexable.rb
Normal file
34
app/models/concerns/indexable.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'elasticsearch/model'
|
||||
|
||||
module Indexable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include Elasticsearch::Model
|
||||
include "#{name}Index".constantize
|
||||
|
||||
after_commit(on: :create) do
|
||||
__elasticsearch__.index_document
|
||||
end
|
||||
|
||||
after_commit(on: :destroy) do
|
||||
__elasticsearch__.delete_document
|
||||
end
|
||||
end
|
||||
|
||||
def update_index(defer: true, priority: :high)
|
||||
if defer
|
||||
if priority == :high
|
||||
IndexUpdateJob.perform_later(self.class.to_s, id)
|
||||
elsif priority == :rebuild
|
||||
IndexRebuildJob.perform_later(self.class.to_s, id)
|
||||
else
|
||||
raise ArgumentError, 'No such priority known'
|
||||
end
|
||||
else
|
||||
__elasticsearch__.index_document
|
||||
end
|
||||
end
|
||||
end
|
9
app/models/story.rb
Normal file
9
app/models/story.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class Story < ApplicationRecord
|
||||
include FancySearchable::Searchable
|
||||
include Indexable
|
||||
|
||||
belongs_to :author
|
||||
has_many :chapters
|
||||
has_many :taggings, validate: false
|
||||
has_many :tags, through: :taggings, validate: false
|
||||
end
|
6
app/models/story/tagging.rb
Normal file
6
app/models/story/tagging.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class Story::Tagging < ApplicationRecord
|
||||
belongs_to :story
|
||||
belongs_to :tag
|
||||
|
||||
validates :tag, uniqueness: { scope: [:story_id] }
|
||||
end
|
2
app/models/tag.rb
Normal file
2
app/models/tag.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
class Tag < ApplicationRecord
|
||||
end
|
29
app/views/chapters/show.html.slim
Normal file
29
app/views/chapters/show.html.slim
Normal file
|
@ -0,0 +1,29 @@
|
|||
body.story
|
||||
#wrap
|
||||
header.banner.main
|
||||
= link_to '/'
|
||||
= image_tag '/img/banner.png'
|
||||
article#story.fic-data.hyphenate
|
||||
header
|
||||
h1
|
||||
= link_to @story.title, story_path(@story)
|
||||
span.author
|
||||
' by
|
||||
= link_to @story.author.name, author_path(@story.author)
|
||||
hr
|
||||
h2= @chapter.title
|
||||
span.chapnav.prev
|
||||
- if @chapter.number == 1
|
||||
= link_to 'Load Full Story', story_chapter_path(@story, 0)
|
||||
- else
|
||||
= link_to 'Previous Chapter', story_chapter_path(@story, @chapter.number - 1)
|
||||
span.chapnav.next
|
||||
- if @chapter.number < @story.chapters.count
|
||||
= link_to 'Next Chapter', story_chapter_path(@story, @chapter.number + 1)
|
||||
hr
|
||||
== @chapter.body
|
||||
|
||||
span.chapnav.bot
|
||||
- if @chapter.number < @story.chapters.count
|
||||
= link_to 'Next Chapter', story_chapter_path(@story, @chapter.number + 1)
|
||||
|
8
app/views/kaminari/_first_page.html.slim
Normal file
8
app/views/kaminari/_first_page.html.slim
Normal file
|
@ -0,0 +1,8 @@
|
|||
/ Link to the "First" page
|
||||
- available local variables
|
||||
url : url to the first page
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
= link_to_unless current_page.first?, '« First', url, remote: remote
|
7
app/views/kaminari/_gap.html.slim
Normal file
7
app/views/kaminari/_gap.html.slim
Normal file
|
@ -0,0 +1,7 @@
|
|||
/ Non-link tag that stands for skipped pages...
|
||||
- available local variables
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
span.page.gap …
|
8
app/views/kaminari/_last_page.html.slim
Normal file
8
app/views/kaminari/_last_page.html.slim
Normal file
|
@ -0,0 +1,8 @@
|
|||
/ Link to the "Last" page
|
||||
- available local variables
|
||||
url : url to the last page
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
= link_to_unless current_page.last?, 'Last »', url, remote: remote
|
8
app/views/kaminari/_next_page.html.slim
Normal file
8
app/views/kaminari/_next_page.html.slim
Normal file
|
@ -0,0 +1,8 @@
|
|||
/ Link to the "Next" page
|
||||
- available local variables
|
||||
url : url to the next page
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
= link_to_unless current_page.last?, 'Next ›', url, rel: 'next', remote: remote, title: 'Next Page (k)', class: 'js-next'
|
12
app/views/kaminari/_page.html.slim
Normal file
12
app/views/kaminari/_page.html.slim
Normal file
|
@ -0,0 +1,12 @@
|
|||
/ Link showing page number
|
||||
- available local variables
|
||||
page : a page object for "this" page
|
||||
url : url to this page
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
- if page.current?
|
||||
span class="page-current" = page
|
||||
- else
|
||||
= link_to page, url, remote: remote, rel: page.rel, class: 'page'
|
18
app/views/kaminari/_paginator.html.slim
Normal file
18
app/views/kaminari/_paginator.html.slim
Normal file
|
@ -0,0 +1,18 @@
|
|||
/ The container tag
|
||||
- available local variables
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
paginator : the paginator that renders the pagination tags inside
|
||||
== paginator.render do
|
||||
nav.pagination
|
||||
==> first_page_tag unless current_page.first?
|
||||
==> prev_page_tag unless current_page.first?
|
||||
- each_page do |page|
|
||||
- if page.display_tag?
|
||||
==> page_tag page
|
||||
- elsif !page.was_truncated?
|
||||
==> gap_tag
|
||||
==> next_page_tag unless current_page.last?
|
||||
== last_page_tag unless current_page.last?
|
8
app/views/kaminari/_prev_page.html.slim
Normal file
8
app/views/kaminari/_prev_page.html.slim
Normal file
|
@ -0,0 +1,8 @@
|
|||
/ Link to the "Previous" page
|
||||
- available local variables
|
||||
url : url to the previous page
|
||||
current_page : a page object for the currently displayed page
|
||||
total_pages : total number of pages
|
||||
per_page : number of items to fetch per page
|
||||
remote : data-remote
|
||||
= link_to_unless current_page.first?, '‹ Prev', url, rel: 'prev', remote: remote, title: 'Previous Page (j)', class: 'js-prev'
|
3
app/views/layouts/_banner.html.slim
Normal file
3
app/views/layouts/_banner.html.slim
Normal file
|
@ -0,0 +1,3 @@
|
|||
header.banner.main
|
||||
= link_to '/'
|
||||
= image_tag '/img/banner.png'
|
35
app/views/layouts/application.html.slim
Normal file
35
app/views/layouts/application.html.slim
Normal file
|
@ -0,0 +1,35 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
- title_ext = yield :title
|
||||
title= title_ext.present? ? "#{title_ext} - FoalFetch" : "FoalFetch - The Uncensored FiM Fiction Archive"
|
||||
meta charset="utf-8"
|
||||
meta name="viewport" content="width=device-width,initial-scale=1"
|
||||
= csrf_meta_tags
|
||||
= csp_meta_tag
|
||||
= stylesheet_link_tag 'application'
|
||||
= javascript_include_tag 'application'
|
||||
link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet"
|
||||
body
|
||||
= yield
|
||||
footer
|
||||
div
|
||||
section
|
||||
h4 Stats
|
||||
span.stat Page generated in #{render_time}
|
||||
/ span.stat FIXME registered users
|
||||
/ span.stat FIXME visitors total
|
||||
section
|
||||
h4 FoalFetch
|
||||
span.stat Frontend designed by DataByte, backend coded by Floorb.
|
||||
span.stat
|
||||
a href="/about" About FoalFetch
|
||||
span.stat.disclaimer
|
||||
' This site and its contents are not affiliated with Hasbro, Inc or FiMFetch.
|
||||
| My Little Pony: Friendship is Magic and all its characters are trademarks of Hasbro, Inc.
|
||||
section
|
||||
h4 Friends
|
||||
span.stat
|
||||
a href="https://twibooru.org" Twibooru
|
||||
span.stat
|
||||
a href="https://ponepaste.org" PonePaste
|
127
app/views/search/_advanced.html.slim
Normal file
127
app/views/search/_advanced.html.slim
Normal file
|
@ -0,0 +1,127 @@
|
|||
label.fake-link.search-adv-link for="show-advanced"
|
||||
| [Toggle Advanced Options]
|
||||
input type="checkbox" id="show-advanced" style="display: none;"
|
||||
div.search-adv
|
||||
.cols
|
||||
small More options coming soon!
|
||||
.cols
|
||||
.opts
|
||||
b Rating
|
||||
br
|
||||
= label_tag "ratings_everyone"
|
||||
= check_box_tag "ratings[everyone]", 1, @search_params.dig('ratings', 'everyone').present?
|
||||
| Everyone
|
||||
= label_tag "ratings_teen"
|
||||
= check_box_tag "ratings[teen]", 1, @search_params.dig('ratings', 'teen').present?
|
||||
| Teen
|
||||
= label_tag "ratings_mature"
|
||||
= check_box_tag "ratings[mature]", 1, @search_params.dig('ratings', 'mature').present?
|
||||
| Mature
|
||||
.opts
|
||||
b Story State
|
||||
br
|
||||
= label_tag "state_complete"
|
||||
= check_box_tag "state[complete]", 1, @search_params.dig('state', 'complete').present?
|
||||
| Complete
|
||||
= label_tag "state_incomplete"
|
||||
= check_box_tag "state[incomplete]", 1, @search_params.dig('state', 'incomplete').present?
|
||||
| Incomplete
|
||||
= label_tag "state_on_hiatus"
|
||||
= check_box_tag "state[on_hiatus]", 1, @search_params.dig('state', 'on_hiatus').present?
|
||||
| On Hiatus
|
||||
= label_tag "state_cancelled"
|
||||
= check_box_tag "state[cancelled]", 1, @search_params.dig('state', 'cancelled').present?
|
||||
| Cancelled
|
||||
/.opts
|
||||
b Story Age
|
||||
br
|
||||
= label_tag
|
||||
= radio_button_tag 'ac', 'lt', checked: 'checked'
|
||||
| Newer Than
|
||||
= label_tag
|
||||
= radio_button_tag 'ac', 'gt'
|
||||
| Older Than
|
||||
= select_tag :age, options_for_select({ \
|
||||
'30 days' => 30, '90 days' => 90, '180 days' => 180, '1 year' => 365, \
|
||||
'2 years' => 730, '3 years' => 1095, '4 years' => 1460, '5 years' => 1825, \
|
||||
'6 years' => 2190, '7 years' => 2555, '8 years' => 2920, '9 years' => 3285, '10 years' => 3650})
|
||||
/.opts
|
||||
b Removed Stories
|
||||
br
|
||||
= select_tag "removed", options_for_select({'Include' => 1, 'Exclude' => 0, 'Only' => 'o'})
|
||||
.cols
|
||||
/.opts
|
||||
b Likes:
|
||||
br
|
||||
= label_tag
|
||||
= radio_button_tag 'lc', 'gt', checked: 'checked'
|
||||
| More Than
|
||||
= label_tag
|
||||
= radio_button_tag 'lc', 'lt'
|
||||
| Less Than
|
||||
= number_field_tag 'likes'
|
||||
/.opts
|
||||
b Words:
|
||||
br
|
||||
= label_tag
|
||||
= radio_button_tag 'wc', 'gt', checked: 'checked'
|
||||
| More Than
|
||||
= label_tag
|
||||
= radio_button_tag 'wc', 'lt'
|
||||
| Less Than
|
||||
= number_field_tag 'words'
|
||||
.opts
|
||||
b Author:
|
||||
br
|
||||
= text_field_tag :author, @search_params['author']
|
||||
/.cols
|
||||
.opts
|
||||
b FiMFiction Rating:
|
||||
br
|
||||
= label_tag
|
||||
= radio_button_tag 'rc', 'gt', checked: 'checked'
|
||||
| More Than
|
||||
= label_tag
|
||||
= radio_button_tag 'rc', 'lt'
|
||||
| Less Than
|
||||
div.stars-sm
|
||||
= radio_button_tag :stars, 5, true, class: 'star star-5'
|
||||
= label_tag 'star_5', '', class: 'star'
|
||||
= radio_button_tag :stars, 4, true, class: 'star star-4'
|
||||
= label_tag 'star_4', '', class: 'star'
|
||||
= radio_button_tag :stars, 3, true, class: 'star star-3'
|
||||
= label_tag 'star_3', '', class: 'star'
|
||||
= radio_button_tag :stars, 2, true, class: 'star star-2'
|
||||
= label_tag 'star_2', '', class: 'star'
|
||||
= radio_button_tag :stars, 1, true, class: 'star star-1'
|
||||
= label_tag 'star_1', '', class: 'star'
|
||||
.cols
|
||||
.opts
|
||||
b Characters
|
||||
br
|
||||
noscript
|
||||
p Please enable JavaScript if you wish to have a more friendly tag searching experience. Otherwise, ignore the dropdown and separate exact tag names by commas in the field.
|
||||
.js-tag-editor
|
||||
.selected-tags
|
||||
= text_field_tag :characters, @search_params['characters']
|
||||
= select_tag :fancy_tags, options_for_select(@character_tags)
|
||||
.cols
|
||||
.opts
|
||||
b Tags
|
||||
br
|
||||
noscript
|
||||
p Please enable JavaScript if you wish to have a more friendly tag searching experience. Otherwise, ignore the dropdown and separate exact tag names by commas in the field.
|
||||
.js-tag-editor
|
||||
.selected-tags
|
||||
= text_field_tag :tags, @search_params['tags']
|
||||
= select_tag :fancy_characters, options_for_select(@other_tags)
|
||||
.cols
|
||||
.opts
|
||||
b Sort By
|
||||
br
|
||||
=> select_tag :sf, options_for_select({'Query Relevance' => 'rel', 'Title' => 'title', 'Author' => 'author', \
|
||||
'Publish Date' => 'date_published', 'Updated Date' => 'date_updated', 'Word Count' => 'num_words'}, @search_params['sf'])
|
||||
= select_tag :sd, options_for_select({'High to Low' => 'desc', 'Low to High' => 'asc'}, @search_params['sd'])
|
||||
- if show_button
|
||||
.buttons
|
||||
= submit_tag 'Go Fetch!', name: 'search'
|
15
app/views/search/index.html.slim
Normal file
15
app/views/search/index.html.slim
Normal file
|
@ -0,0 +1,15 @@
|
|||
.main
|
||||
#wrap
|
||||
nav.home
|
||||
= link_to "Daily Fictions", "/ficoftheday"
|
||||
= link_to "News", "/news"
|
||||
= link_to "About", "/about"
|
||||
section.search.search-l
|
||||
img alt="FoalFetch" src="/img/banner.png"
|
||||
= form_tag "/search", method: :post
|
||||
.searchbox
|
||||
= search_field_tag "q", nil, placeholder: 'Search story titles...'
|
||||
= render partial: 'advanced', locals: { show_button: false }
|
||||
.buttons
|
||||
= submit_tag 'Go Fetch!', name: 'search'
|
||||
= submit_tag 'Pick one for me!', name: 'luck'
|
58
app/views/search/search.html.slim
Normal file
58
app/views/search/search.html.slim
Normal file
|
@ -0,0 +1,58 @@
|
|||
.main
|
||||
#wrap
|
||||
section.search.search-s
|
||||
= form_tag '/search', method: :post
|
||||
a.logo href="/"
|
||||
= image_tag '/img/logo_small.png'
|
||||
.searchbox
|
||||
= search_field_tag 'q', @search_params['q'], placeholder: 'Search story titles...'
|
||||
= render partial: 'advanced', locals: { show_button: true }
|
||||
|
||||
- @records.each do |rec|
|
||||
- character_tags = rec.tags.where(type: 'character')
|
||||
- normal_tags = rec.tags.where.not(type: 'character')
|
||||
section.result
|
||||
section.fic-cell
|
||||
header
|
||||
- if rec.cover_image
|
||||
= image_tag '/images?url=' + CGI.escape(rec.cover_image), alt: 'Cover Image'
|
||||
.details
|
||||
h2= link_to rec.title, story_path(rec)
|
||||
span.author
|
||||
' by
|
||||
= link_to rec.author.name, author_path(rec.author)
|
||||
br
|
||||
span.popular
|
||||
span.stats
|
||||
p.description= rec.short_description
|
||||
footer
|
||||
.rating
|
||||
= rating_display(rec.rating)
|
||||
= status_display(rec.completion_status)
|
||||
- normal_tags.each do |t|
|
||||
= tag_to_html(t)
|
||||
.characters
|
||||
- character_tags.each do |t|
|
||||
= character_display(t)
|
||||
br
|
||||
.ficstats
|
||||
span.chapters
|
||||
= pluralize(rec.chapters.count, 'Chapter') + ','
|
||||
'
|
||||
span.words
|
||||
=<> rec.num_words
|
||||
' Words:
|
||||
span.time_est
|
||||
' Estimated
|
||||
=> reading_time(rec.num_words)
|
||||
| to read
|
||||
.published
|
||||
' Published
|
||||
span= rec.date_published
|
||||
.updated
|
||||
' Last Update
|
||||
span= rec.date_updated
|
||||
.searchnav
|
||||
= paginate(@records, params: { scope: @scope_key })
|
||||
br
|
||||
span.searchtime Found #{@search.total_count} results.
|
25
app/views/static_pages/about.html.slim
Normal file
25
app/views/static_pages/about.html.slim
Normal file
|
@ -0,0 +1,25 @@
|
|||
- content_for :title, 'About'
|
||||
.about
|
||||
#wrap
|
||||
= render partial: 'layouts/banner'
|
||||
nav.home
|
||||
= link_to 'Home', '/'
|
||||
= link_to 'News', '/news'
|
||||
= link_to 'About', '/about', class: 'current'
|
||||
article
|
||||
section
|
||||
h1 About FoalFetch
|
||||
p FoalFetch is a <b>uncensored</b> archive of My Little Pony: Friendship is Magic fan fiction works. Born after
|
||||
DataByte began censoring FiMFetch, the project provides an ever-growing list of features:
|
||||
ul
|
||||
li Archive of active and past works of fiction
|
||||
li Easy-to-use granular search features
|
||||
li Random daily fictions
|
||||
li Multiple downloadable formats
|
||||
|
||||
p And some long-term goals:
|
||||
ul
|
||||
li Scrape multiple fanfiction platforms for MLP:FiM stories
|
||||
li An OPDS browsing service for compatible e-Readers
|
||||
|
||||
p This site has been designed from the ground-up in an attempt to provide the simplest and most streamlined access to all the pony you should ever want - even content the site's operators don't agree with.
|
0
app/views/stories/index.html.slim
Normal file
0
app/views/stories/index.html.slim
Normal file
52
app/views/stories/show.html.slim
Normal file
52
app/views/stories/show.html.slim
Normal file
|
@ -0,0 +1,52 @@
|
|||
- content_for :title, @story.title
|
||||
div.story
|
||||
div#wrap
|
||||
= render partial: 'layouts/banner'
|
||||
section.fic-cell.large
|
||||
header
|
||||
- if @story.cover_image
|
||||
a
|
||||
= image_tag '/images?url=' + CGI.escape(@story.cover_image), alt: 'Cover Image'
|
||||
div.details
|
||||
h2= @story.title
|
||||
span.author
|
||||
' by
|
||||
= link_to @story.author.name, author_path(@story.author)
|
||||
div.desc_short
|
||||
= @story.short_description
|
||||
span.popular
|
||||
/ likes, etc
|
||||
section.description#desc
|
||||
p
|
||||
== @story.description_html
|
||||
section
|
||||
.rating
|
||||
=> rating_display(@story.content_rating)
|
||||
=> status_display(@story.completion_status)
|
||||
'
|
||||
.ficstats
|
||||
span.words> #{@story.num_words} words:
|
||||
span.time_est
|
||||
' Estimated
|
||||
=> reading_time(@story.num_words)
|
||||
| to read
|
||||
span.cached
|
||||
.dl-links
|
||||
span Download:
|
||||
.chapterlist
|
||||
h3= pluralize(@chapters.count, 'Chapter') + ':'
|
||||
ol
|
||||
- @chapters.each do |c|
|
||||
li
|
||||
span.chapter_title
|
||||
=> link_to c.title, story_chapter_path(@story, c.number)
|
||||
span.date= c.date_published
|
||||
.word_count= c.num_words
|
||||
footer
|
||||
.published
|
||||
' Published
|
||||
span= @story.date_published
|
||||
.updated
|
||||
' Last Update
|
||||
span= @story.date_modified
|
||||
|
109
bin/bundle
Executable file
109
bin/bundle
Executable file
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'bundle' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "rubygems"
|
||||
|
||||
m = Module.new do
|
||||
module_function
|
||||
|
||||
def invoked_as_script?
|
||||
File.expand_path($0) == File.expand_path(__FILE__)
|
||||
end
|
||||
|
||||
def env_var_version
|
||||
ENV["BUNDLER_VERSION"]
|
||||
end
|
||||
|
||||
def cli_arg_version
|
||||
return unless invoked_as_script? # don't want to hijack other binstubs
|
||||
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
||||
bundler_version = nil
|
||||
update_index = nil
|
||||
ARGV.each_with_index do |a, i|
|
||||
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
||||
bundler_version = a
|
||||
end
|
||||
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
||||
bundler_version = $1
|
||||
update_index = i
|
||||
end
|
||||
bundler_version
|
||||
end
|
||||
|
||||
def gemfile
|
||||
gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
return gemfile if gemfile && !gemfile.empty?
|
||||
|
||||
File.expand_path("../Gemfile", __dir__)
|
||||
end
|
||||
|
||||
def lockfile
|
||||
lockfile =
|
||||
case File.basename(gemfile)
|
||||
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
|
||||
else "#{gemfile}.lock"
|
||||
end
|
||||
File.expand_path(lockfile)
|
||||
end
|
||||
|
||||
def lockfile_version
|
||||
return unless File.file?(lockfile)
|
||||
lockfile_contents = File.read(lockfile)
|
||||
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
||||
Regexp.last_match(1)
|
||||
end
|
||||
|
||||
def bundler_requirement
|
||||
@bundler_requirement ||=
|
||||
env_var_version ||
|
||||
cli_arg_version ||
|
||||
bundler_requirement_for(lockfile_version)
|
||||
end
|
||||
|
||||
def bundler_requirement_for(version)
|
||||
return "#{Gem::Requirement.default}.a" unless version
|
||||
|
||||
bundler_gem_version = Gem::Version.new(version)
|
||||
|
||||
bundler_gem_version.approximate_recommendation
|
||||
end
|
||||
|
||||
def load_bundler!
|
||||
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
||||
|
||||
activate_bundler
|
||||
end
|
||||
|
||||
def activate_bundler
|
||||
gem_error = activation_error_handling do
|
||||
gem "bundler", bundler_requirement
|
||||
end
|
||||
return if gem_error.nil?
|
||||
require_error = activation_error_handling do
|
||||
require "bundler/version"
|
||||
end
|
||||
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
||||
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
||||
exit 42
|
||||
end
|
||||
|
||||
def activation_error_handling
|
||||
yield
|
||||
nil
|
||||
rescue StandardError, LoadError => e
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
m.load_bundler!
|
||||
|
||||
if m.invoked_as_script?
|
||||
load Gem.bin_path("bundler", "bundle")
|
||||
end
|
4
bin/importmap
Executable file
4
bin/importmap
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/application"
|
||||
require "importmap/commands"
|
4
bin/rails
Executable file
4
bin/rails
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
APP_PATH = File.expand_path("../config/application", __dir__)
|
||||
require_relative "../config/boot"
|
||||
require "rails/commands"
|
4
bin/rake
Executable file
4
bin/rake
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
require_relative "../config/boot"
|
||||
require "rake"
|
||||
Rake.application.run
|
33
bin/setup
Executable file
33
bin/setup
Executable file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env ruby
|
||||
require "fileutils"
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
end
|
||||
|
||||
FileUtils.chdir APP_ROOT do
|
||||
# This script is a way to set up or update your development environment automatically.
|
||||
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
|
||||
# Add necessary setup steps to this file.
|
||||
|
||||
puts "== Installing dependencies =="
|
||||
system! "gem install bundler --conservative"
|
||||
system("bundle check") || system!("bundle install")
|
||||
|
||||
# puts "\n== Copying sample files =="
|
||||
# unless File.exist?("config/database.yml")
|
||||
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
|
||||
# end
|
||||
|
||||
puts "\n== Preparing database =="
|
||||
system! "bin/rails db:prepare"
|
||||
|
||||
puts "\n== Removing old logs and tempfiles =="
|
||||
system! "bin/rails log:clear tmp:clear"
|
||||
|
||||
puts "\n== Restarting application server =="
|
||||
system! "bin/rails restart"
|
||||
end
|
6
config.ru
Normal file
6
config.ru
Normal file
|
@ -0,0 +1,6 @@
|
|||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
require_relative "config/environment"
|
||||
|
||||
run Rails.application
|
||||
Rails.application.load_server
|
34
config/application.rb
Normal file
34
config/application.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
require_relative "boot"
|
||||
|
||||
require "rails"
|
||||
# Pick the frameworks you want:
|
||||
require "active_model/railtie"
|
||||
require "active_job/railtie"
|
||||
require "active_record/railtie"
|
||||
# require "active_storage/engine"
|
||||
require "action_controller/railtie"
|
||||
# require "action_mailer/railtie"
|
||||
# require "action_mailbox/engine"
|
||||
# require "action_text/engine"
|
||||
require "action_view/railtie"
|
||||
require "action_cable/engine"
|
||||
require "rails/test_unit/railtie"
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
module Foalfetch
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
config.load_defaults 7.0
|
||||
|
||||
# Configuration for the application, engines, and railties goes here.
|
||||
#
|
||||
# These settings can be overridden in specific environments using the files
|
||||
# in config/environments, which are processed later.
|
||||
#
|
||||
# config.time_zone = "Central Time (US & Canada)"
|
||||
# config.eager_load_paths << Rails.root.join("extras")
|
||||
end
|
||||
end
|
3
config/boot.rb
Normal file
3
config/boot.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
require "bundler/setup" # Set up gems listed in the Gemfile.
|
11
config/cable.yml
Normal file
11
config/cable.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
development:
|
||||
adapter: redis
|
||||
url: redis://localhost:6379/1
|
||||
|
||||
test:
|
||||
adapter: test
|
||||
|
||||
production:
|
||||
adapter: redis
|
||||
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||
channel_prefix: foalfetch_production
|
1
config/credentials.yml.enc
Normal file
1
config/credentials.yml.enc
Normal file
|
@ -0,0 +1 @@
|
|||
tL8+fuPHdTfbSgNCZVdXNdQ+wKg+87PpLyl5vgFSiqX1lg0WBNv43li9xM4kUU4Zj7YmjS49yqsyOFMhrW+g31Hh+cLZ3fx97XPVV97tu6/l8Dg9Ornldm6+wxg5zpOCrQyvF66DFouH9wMgw0iaEM3wWsvZjFDtsaa/kpgpPqGNUWhMFF6RNnbLeuGN22sm3d6FKEOFa0Ekl+YnKoxnbywshyG3+mAgpLAMt73u62KjiZsgIOku81k44WKDFLqqIeQdEumrWD8IxGFVIVPPm8xzGYq+K5LnIIIF0cw5coEVHZGU904fVvrVxmb0sMU5ZmozM6e98D86SpAQYj6LLFGbKFcZFBgiLknlLnkXDtwIN6chSUNT9lQmSqN4cL8byDwXI2X9hjugIcV7Qn/nGRJ1b4tOrWV/tWo/--CmilwP4O5Ch/FIvK--JD2c8xj2FnJJsSsRAU8HoQ==
|
86
config/database.yml
Normal file
86
config/database.yml
Normal file
|
@ -0,0 +1,86 @@
|
|||
# PostgreSQL. Versions 9.3 and up are supported.
|
||||
#
|
||||
# Install the pg driver:
|
||||
# gem install pg
|
||||
# On macOS with Homebrew:
|
||||
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
|
||||
# On macOS with MacPorts:
|
||||
# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
|
||||
# On Windows:
|
||||
# gem install pg
|
||||
# Choose the win32 build.
|
||||
# Install PostgreSQL and put its /bin directory on your path.
|
||||
#
|
||||
# Configure Using Gemfile
|
||||
# gem "pg"
|
||||
#
|
||||
default: &default
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
# For details on connection pooling, see Rails configuration guide
|
||||
# https://guides.rubyonrails.org/configuring.html#database-pooling
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
database: foalfetch_development
|
||||
|
||||
# The specified database role being used to connect to postgres.
|
||||
# To create additional roles in postgres see `$ createuser --help`.
|
||||
# When left blank, postgres will use the default role. This is
|
||||
# the same name as the operating system user running Rails.
|
||||
#username: foalfetch
|
||||
|
||||
# The password associated with the postgres role (username).
|
||||
#password:
|
||||
|
||||
# Connect on a TCP socket. Omitted by default since the client uses a
|
||||
# domain socket that doesn't need configuration. Windows does not have
|
||||
# domain sockets, so uncomment these lines.
|
||||
#host: localhost
|
||||
|
||||
# The TCP port the server listens on. Defaults to 5432.
|
||||
# If your server runs on a different port number, change accordingly.
|
||||
#port: 5432
|
||||
|
||||
# Schema search path. The server defaults to $user,public
|
||||
#schema_search_path: myapp,sharedapp,public
|
||||
|
||||
# Minimum log levels, in increasing order:
|
||||
# debug5, debug4, debug3, debug2, debug1,
|
||||
# log, notice, warning, error, fatal, and panic
|
||||
# Defaults to warning.
|
||||
#min_messages: notice
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
<<: *default
|
||||
database: foalfetch_test
|
||||
|
||||
# As with config/credentials.yml, you never want to store sensitive information,
|
||||
# like your database password, in your source code. If your source code is
|
||||
# ever seen by anyone, they now have access to your database.
|
||||
#
|
||||
# Instead, provide the password or a full connection URL as an environment
|
||||
# variable when you boot the app. For example:
|
||||
#
|
||||
# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
|
||||
#
|
||||
# If the connection URL is provided in the special DATABASE_URL environment
|
||||
# variable, Rails will automatically merge its configuration values on top of
|
||||
# the values provided in this file. Alternatively, you can specify a connection
|
||||
# URL environment variable explicitly:
|
||||
#
|
||||
# production:
|
||||
# url: <%= ENV["MY_APP_DATABASE_URL"] %>
|
||||
#
|
||||
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
|
||||
# for a full overview on how database connection configuration can be specified.
|
||||
#
|
||||
production:
|
||||
<<: *default
|
||||
database: foalfetch_production
|
||||
username: foalfetch
|
||||
password: <%= ENV["FOALFETCH_DATABASE_PASSWORD"] %>
|
5
config/environment.rb
Normal file
5
config/environment.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Load the Rails application.
|
||||
require_relative "application"
|
||||
|
||||
# Initialize the Rails application.
|
||||
Rails.application.initialize!
|
62
config/environments/development.rb
Normal file
62
config/environments/development.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
require "active_support/core_ext/integer/time"
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# In the development environment your application's code is reloaded any time
|
||||
# it changes. This slows down response time but is perfect for development
|
||||
# since you don't have to restart the web server when you make code changes.
|
||||
config.cache_classes = false
|
||||
|
||||
# Do not eager load code on boot.
|
||||
config.eager_load = false
|
||||
|
||||
# Show full error reports.
|
||||
config.consider_all_requests_local = true
|
||||
|
||||
# Enable server timing
|
||||
config.server_timing = true
|
||||
|
||||
# Enable/disable caching. By default caching is disabled.
|
||||
# Run rails dev:cache to toggle caching.
|
||||
if Rails.root.join("tmp/caching-dev.txt").exist?
|
||||
config.action_controller.perform_caching = true
|
||||
config.action_controller.enable_fragment_cache_logging = true
|
||||
|
||||
config.cache_store = :memory_store
|
||||
config.public_file_server.headers = {
|
||||
"Cache-Control" => "public, max-age=#{2.days.to_i}"
|
||||
}
|
||||
else
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
config.cache_store = :null_store
|
||||
end
|
||||
|
||||
# Print deprecation notices to the Rails logger.
|
||||
config.active_support.deprecation = :log
|
||||
|
||||
# Raise exceptions for disallowed deprecations.
|
||||
config.active_support.disallowed_deprecation = :raise
|
||||
|
||||
# Tell Active Support which deprecation messages to disallow.
|
||||
config.active_support.disallowed_deprecation_warnings = []
|
||||
|
||||
# Raise an error on page load if there are pending migrations.
|
||||
config.active_record.migration_error = :page_load
|
||||
|
||||
# Highlight code that triggered database queries in logs.
|
||||
config.active_record.verbose_query_logs = true
|
||||
|
||||
# Suppress logger output for asset requests.
|
||||
config.assets.quiet = true
|
||||
|
||||
# Raises error for missing translations.
|
||||
# config.i18n.raise_on_missing_translations = true
|
||||
|
||||
# Annotate rendered view with file names.
|
||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||
|
||||
# Uncomment if you wish to allow Action Cable access from any origin.
|
||||
# config.action_cable.disable_request_forgery_protection = true
|
||||
end
|
84
config/environments/production.rb
Normal file
84
config/environments/production.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
require "active_support/core_ext/integer/time"
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# Code is not reloaded between requests.
|
||||
config.cache_classes = true
|
||||
|
||||
# Eager load code on boot. This eager loads most of Rails and
|
||||
# your application in memory, allowing both threaded web servers
|
||||
# and those relying on copy on write to perform better.
|
||||
# Rake tasks automatically ignore this option for performance.
|
||||
config.eager_load = true
|
||||
|
||||
# Full error reports are disabled and caching is turned on.
|
||||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
||||
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
||||
# config.require_master_key = true
|
||||
|
||||
# Disable serving static files from the `/public` folder by default since
|
||||
# Apache or NGINX already handles this.
|
||||
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
|
||||
|
||||
# Compress CSS using a preprocessor.
|
||||
# config.assets.css_compressor = :sass
|
||||
|
||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||
config.assets.compile = false
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||
# config.asset_host = "http://assets.example.com"
|
||||
|
||||
# Specifies the header that your server uses for sending files.
|
||||
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
|
||||
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
|
||||
|
||||
# Mount Action Cable outside main process or domain.
|
||||
# config.action_cable.mount_path = nil
|
||||
# config.action_cable.url = "wss://example.com/cable"
|
||||
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
|
||||
|
||||
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||
# config.force_ssl = true
|
||||
|
||||
# Include generic and useful information about system operation, but avoid logging too much
|
||||
# information to avoid inadvertent exposure of personally identifiable information (PII).
|
||||
config.log_level = :info
|
||||
|
||||
# Prepend all log lines with the following tags.
|
||||
config.log_tags = [ :request_id ]
|
||||
|
||||
# Use a different cache store in production.
|
||||
# config.cache_store = :mem_cache_store
|
||||
|
||||
# Use a real queuing backend for Active Job (and separate queues per environment).
|
||||
# config.active_job.queue_adapter = :resque
|
||||
# config.active_job.queue_name_prefix = "foalfetch_production"
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
# Don't log any deprecations.
|
||||
config.active_support.report_deprecations = false
|
||||
|
||||
# Use default logging formatter so that PID and timestamp are not suppressed.
|
||||
config.log_formatter = ::Logger::Formatter.new
|
||||
|
||||
# Use a different logger for distributed setups.
|
||||
# require "syslog/logger"
|
||||
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
|
||||
|
||||
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
||||
logger = ActiveSupport::Logger.new(STDOUT)
|
||||
logger.formatter = config.log_formatter
|
||||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||
end
|
||||
|
||||
# Do not dump schema after migrations.
|
||||
config.active_record.dump_schema_after_migration = false
|
||||
end
|
50
config/environments/test.rb
Normal file
50
config/environments/test.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
require "active_support/core_ext/integer/time"
|
||||
|
||||
# The test environment is used exclusively to run your application's
|
||||
# test suite. You never need to work with it otherwise. Remember that
|
||||
# your test database is "scratch space" for the test suite and is wiped
|
||||
# and recreated between test runs. Don't rely on the data there!
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# Turn false under Spring and add config.action_view.cache_template_loading = true.
|
||||
config.cache_classes = true
|
||||
|
||||
# Eager loading loads your whole application. When running a single test locally,
|
||||
# this probably isn't necessary. It's a good idea to do in a continuous integration
|
||||
# system, or in some way before deploying your code.
|
||||
config.eager_load = ENV["CI"].present?
|
||||
|
||||
# Configure public file server for tests with Cache-Control for performance.
|
||||
config.public_file_server.enabled = true
|
||||
config.public_file_server.headers = {
|
||||
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
|
||||
}
|
||||
|
||||
# Show full error reports and disable caching.
|
||||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = false
|
||||
config.cache_store = :null_store
|
||||
|
||||
# Raise exceptions instead of rendering exception templates.
|
||||
config.action_dispatch.show_exceptions = false
|
||||
|
||||
# Disable request forgery protection in test environment.
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
# Print deprecation notices to the stderr.
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
# Raise exceptions for disallowed deprecations.
|
||||
config.active_support.disallowed_deprecation = :raise
|
||||
|
||||
# Tell Active Support which deprecation messages to disallow.
|
||||
config.active_support.disallowed_deprecation_warnings = []
|
||||
|
||||
# Raises error for missing translations.
|
||||
# config.i18n.raise_on_missing_translations = true
|
||||
|
||||
# Annotate rendered view with file names.
|
||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||
end
|
7
config/importmap.rb
Normal file
7
config/importmap.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Pin npm packages by running ./bin/importmap
|
||||
|
||||
pin "application"
|
||||
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
||||
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
12
config/initializers/assets.rb
Normal file
12
config/initializers/assets.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Version of your assets, change this if you want to expire all your assets.
|
||||
Rails.application.config.assets.version = "1.0"
|
||||
|
||||
# Add additional assets to the asset load path.
|
||||
# Rails.application.config.assets.paths << Emoji.images_path
|
||||
|
||||
# Precompile additional assets.
|
||||
# application.js, application.css, and all non-JS/CSS in the app/assets
|
||||
# folder are already added.
|
||||
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
25
config/initializers/content_security_policy.rb
Normal file
25
config/initializers/content_security_policy.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Define an application-wide content security policy.
|
||||
# See the Securing Rails Applications Guide for more information:
|
||||
# https://guides.rubyonrails.org/security.html#content-security-policy-header
|
||||
|
||||
# Rails.application.configure do
|
||||
# config.content_security_policy do |policy|
|
||||
# policy.default_src :self, :https
|
||||
# policy.font_src :self, :https, :data
|
||||
# policy.img_src :self, :https, :data
|
||||
# policy.object_src :none
|
||||
# policy.script_src :self, :https
|
||||
# policy.style_src :self, :https
|
||||
# # Specify URI for violation reports
|
||||
# # policy.report_uri "/csp-violation-report-endpoint"
|
||||
# end
|
||||
#
|
||||
# # Generate session nonces for permitted importmap and inline scripts
|
||||
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
|
||||
# config.content_security_policy_nonce_directives = %w(script-src)
|
||||
#
|
||||
# # Report violations without enforcing the policy.
|
||||
# # config.content_security_policy_report_only = true
|
||||
# end
|
17
config/initializers/elasticsearch.rb
Normal file
17
config/initializers/elasticsearch.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
Elasticsearch::Model.client = Elasticsearch::Client.new(
|
||||
host: 'https://127.0.0.1:9200',
|
||||
http: {
|
||||
user: 'elastic',
|
||||
password: 'FrLV=CCE56dYsK*87jEo'
|
||||
},
|
||||
request_timeout: 30
|
||||
) do |f|
|
||||
f.ssl[:verify] = false
|
||||
end
|
||||
|
||||
# Prevents this message from appearing in logs:
|
||||
# You are setting a key that conflicts with a built-in method Hashie::Mash#key defined in Hash.
|
||||
# This can cause unexpected behavior when accessing the key via as a property.
|
||||
# You can still access the key via the #[] method.
|
||||
Hashie.logger = Logger.new(nil)
|
8
config/initializers/filter_parameter_logging.rb
Normal file
8
config/initializers/filter_parameter_logging.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Configure parameters to be filtered from the log file. Use this to limit dissemination of
|
||||
# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
|
||||
# notations and behaviors.
|
||||
Rails.application.config.filter_parameters += [
|
||||
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
||||
]
|
16
config/initializers/inflections.rb
Normal file
16
config/initializers/inflections.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Add new inflection rules using the following format. Inflections
|
||||
# are locale specific, and you may define rules for as many different
|
||||
# locales as you wish. All of these examples are active by default:
|
||||
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# inflect.plural /^(ox)$/i, "\\1en"
|
||||
# inflect.singular /^(ox)en/i, "\\1"
|
||||
# inflect.irregular "person", "people"
|
||||
# inflect.uncountable %w( fish sheep )
|
||||
# end
|
||||
|
||||
# These inflection rules are supported but not enabled by default:
|
||||
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# inflect.acronym "RESTful"
|
||||
# end
|
11
config/initializers/permissions_policy.rb
Normal file
11
config/initializers/permissions_policy.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Define an application-wide HTTP permissions policy. For further
|
||||
# information see https://developers.google.com/web/updates/2018/06/feature-policy
|
||||
#
|
||||
# Rails.application.config.permissions_policy do |f|
|
||||
# f.camera :none
|
||||
# f.gyroscope :none
|
||||
# f.microphone :none
|
||||
# f.usb :none
|
||||
# f.fullscreen :self
|
||||
# f.payment :self, "https://secure.example.com"
|
||||
# end
|
14
config/initializers/redis.rb
Normal file
14
config/initializers/redis.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
ENV['REDIS_HOST'] ||= 'localhost'
|
||||
|
||||
#require 'hiredis'
|
||||
require 'redis'
|
||||
|
||||
$redis = Redis.new(host: ENV['REDIS_HOST'])
|
||||
|
||||
if defined?(PhusionPassenger)
|
||||
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
||||
$redis.disconnect! if forked
|
||||
end
|
||||
end
|
33
config/locales/en.yml
Normal file
33
config/locales/en.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Files in the config/locales directory are used for internationalization
|
||||
# and are automatically loaded by Rails. If you want to use locales other
|
||||
# than English, add the necessary files in this directory.
|
||||
#
|
||||
# To use the locales, use `I18n.t`:
|
||||
#
|
||||
# I18n.t "hello"
|
||||
#
|
||||
# In views, this is aliased to just `t`:
|
||||
#
|
||||
# <%= t("hello") %>
|
||||
#
|
||||
# To use a different locale, set it with `I18n.locale`:
|
||||
#
|
||||
# I18n.locale = :es
|
||||
#
|
||||
# This would use the information in config/locales/es.yml.
|
||||
#
|
||||
# The following keys must be escaped otherwise they will not be retrieved by
|
||||
# the default I18n backend:
|
||||
#
|
||||
# true, false, on, off, yes, no
|
||||
#
|
||||
# Instead, surround them with single quotes.
|
||||
#
|
||||
# en:
|
||||
# "true": "foo"
|
||||
#
|
||||
# To learn more, please read the Rails Internationalization guide
|
||||
# available at https://guides.rubyonrails.org/i18n.html.
|
||||
|
||||
en:
|
||||
hello: "Hello world"
|
43
config/puma.rb
Normal file
43
config/puma.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Puma can serve each request in a thread from an internal thread pool.
|
||||
# The `threads` method setting takes two numbers: a minimum and maximum.
|
||||
# Any libraries that use thread pools should be configured to match
|
||||
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
||||
# and maximum; this matches the default thread size of Active Record.
|
||||
#
|
||||
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
|
||||
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
||||
threads min_threads_count, max_threads_count
|
||||
|
||||
# Specifies the `worker_timeout` threshold that Puma will use to wait before
|
||||
# terminating a worker in development environments.
|
||||
#
|
||||
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
|
||||
|
||||
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
||||
#
|
||||
port ENV.fetch("PORT") { 3000 }
|
||||
|
||||
# Specifies the `environment` that Puma will run in.
|
||||
#
|
||||
environment ENV.fetch("RAILS_ENV") { "development" }
|
||||
|
||||
# Specifies the `pidfile` that Puma will use.
|
||||
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
|
||||
|
||||
# Specifies the number of `workers` to boot in clustered mode.
|
||||
# Workers are forked web server processes. If using threads and workers together
|
||||
# the concurrency of the application would be max `threads` * `workers`.
|
||||
# Workers do not work on JRuby or Windows (both of which do not support
|
||||
# processes).
|
||||
#
|
||||
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
||||
|
||||
# Use the `preload_app!` method when specifying a `workers` number.
|
||||
# This directive tells Puma to first boot the application and load code
|
||||
# before forking the application. This takes advantage of Copy On Write
|
||||
# process behavior so workers use less memory.
|
||||
#
|
||||
# preload_app!
|
||||
|
||||
# Allow puma to be restarted by `bin/rails restart` command.
|
||||
plugin :tmp_restart
|
12
config/routes.rb
Normal file
12
config/routes.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
Rails.application.routes.draw do
|
||||
root 'search#index'
|
||||
post '/search' => 'search#search'
|
||||
get '/search' => 'search#search'
|
||||
get '/about' => 'static_pages#about'
|
||||
get '/images' => 'images#show'
|
||||
|
||||
resources :authors, only: [:show]
|
||||
resources :stories, only: [:show] do
|
||||
resources :chapters, only: [:show]
|
||||
end
|
||||
end
|
63
db/migrate/20240401101354_initial.rb
Normal file
63
db/migrate/20240401101354_initial.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
class Initial < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
#create_enum :story_completion_status, %w[hiatus incomplete complete cancelled]
|
||||
#create_enum :story_content_rating, %w[teen everyone mature]
|
||||
|
||||
create_table :authors do |t|
|
||||
t.text :name, null: false
|
||||
t.integer :num_blog_posts, null: false, default: 0
|
||||
t.integer :num_followers, null: false, default: 0
|
||||
t.text :avatar, null: true
|
||||
t.text :bio_html, null: true
|
||||
t.datetime :date_joined, null: false
|
||||
t.text :url, null: false
|
||||
end
|
||||
|
||||
create_table :stories do |t|
|
||||
t.belongs_to :author, null: false
|
||||
t.integer :color, null: true
|
||||
# t.enum :completion_status, enum_type: 'story_completion_status', null: false
|
||||
# t.enum :content_rating, enum_type: 'story_content_rating', null: false
|
||||
t.text :completion_status
|
||||
t.text :content_rating
|
||||
t.text :cover_image, null: true
|
||||
t.datetime :date_published, null: false
|
||||
t.datetime :date_updated, null: true
|
||||
t.datetime :date_modified, null: true
|
||||
t.text :description_html, null: true
|
||||
t.integer :num_comments, null: false, default: 0
|
||||
t.integer :num_views, null: false, default: 0
|
||||
t.integer :num_words, null: false
|
||||
t.integer :prequel, null: true
|
||||
t.integer :rating, null: false
|
||||
t.text :short_description, null: true
|
||||
t.text :title, null: false
|
||||
t.integer :total_num_views, null: false, default: 0
|
||||
t.text :url, null: false
|
||||
end
|
||||
|
||||
create_table :chapters do |t|
|
||||
t.belongs_to :story, null: false
|
||||
t.integer :number, null: false, default: 1
|
||||
t.datetime :date_published, null: false
|
||||
t.datetime :date_modified, null: true
|
||||
t.integer :num_views, null: false, default: 0
|
||||
t.integer :num_words, null: false
|
||||
t.text :title, null: false
|
||||
t.text :url, null: false
|
||||
t.text :body, null: false
|
||||
end
|
||||
|
||||
create_table :tags do |t|
|
||||
t.text :name, null: false
|
||||
t.text :old_id, null: true
|
||||
t.text :url, null: false
|
||||
t.text :type, null: false
|
||||
end
|
||||
|
||||
create_table :story_taggings, id: false, primary_key: [:story_id, :tag_id] do |t|
|
||||
t.belongs_to :story, null: false
|
||||
t.belongs_to :tag, null: false
|
||||
end
|
||||
end
|
||||
end
|
5
db/migrate/20240401121829_temp_nullable_body.rb
Normal file
5
db/migrate/20240401121829_temp_nullable_body.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class TempNullableBody < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
change_column_null :chapters, :body, true
|
||||
end
|
||||
end
|
8
db/migrate/20240402172140_remove_urls.rb
Normal file
8
db/migrate/20240402172140_remove_urls.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class RemoveUrls < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
remove_column :stories, :url, :text
|
||||
remove_column :chapters, :url, :text
|
||||
remove_column :tags, :url, :text
|
||||
remove_column :authors, :url, :text
|
||||
end
|
||||
end
|
77
db/schema.rb
generated
Normal file
77
db/schema.rb
generated
Normal file
|
@ -0,0 +1,77 @@
|
|||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_04_02_172140) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
# Custom types defined in this database.
|
||||
# Note that some types may not work with other database engines. Be careful if changing database.
|
||||
create_enum "story_completion_status", ["hiatus", "incomplete", "complete", "cancelled"]
|
||||
create_enum "story_content_rating", ["teen", "everyone", "mature"]
|
||||
|
||||
create_table "authors", force: :cascade do |t|
|
||||
t.text "name", null: false
|
||||
t.integer "num_blog_posts", default: 0, null: false
|
||||
t.integer "num_followers", default: 0, null: false
|
||||
t.text "avatar"
|
||||
t.text "bio_html"
|
||||
t.datetime "date_joined", null: false
|
||||
end
|
||||
|
||||
create_table "chapters", force: :cascade do |t|
|
||||
t.bigint "story_id", null: false
|
||||
t.integer "number", default: 1, null: false
|
||||
t.datetime "date_published", null: false
|
||||
t.datetime "date_modified"
|
||||
t.integer "num_views", default: 0, null: false
|
||||
t.integer "num_words", null: false
|
||||
t.text "title", null: false
|
||||
t.text "body"
|
||||
t.index ["story_id"], name: "index_chapters_on_story_id"
|
||||
end
|
||||
|
||||
create_table "stories", force: :cascade do |t|
|
||||
t.bigint "author_id", null: false
|
||||
t.integer "color"
|
||||
t.text "completion_status"
|
||||
t.text "content_rating"
|
||||
t.text "cover_image"
|
||||
t.datetime "date_published", null: false
|
||||
t.datetime "date_updated"
|
||||
t.datetime "date_modified"
|
||||
t.text "description_html"
|
||||
t.integer "num_comments", default: 0, null: false
|
||||
t.integer "num_views", default: 0, null: false
|
||||
t.integer "num_words", null: false
|
||||
t.integer "prequel"
|
||||
t.integer "rating", null: false
|
||||
t.text "short_description"
|
||||
t.text "title", null: false
|
||||
t.integer "total_num_views", default: 0, null: false
|
||||
t.index ["author_id"], name: "index_stories_on_author_id"
|
||||
end
|
||||
|
||||
create_table "story_taggings", id: false, force: :cascade do |t|
|
||||
t.bigint "story_id", null: false
|
||||
t.bigint "tag_id", null: false
|
||||
t.index ["story_id"], name: "index_story_taggings_on_story_id"
|
||||
t.index ["tag_id"], name: "index_story_taggings_on_tag_id"
|
||||
end
|
||||
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.text "name", null: false
|
||||
t.text "old_id"
|
||||
t.text "type", null: false
|
||||
end
|
||||
|
||||
end
|
7
db/seeds.rb
Normal file
7
db/seeds.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file should contain all the record creation needed to seed the database with its default values.
|
||||
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
|
||||
# Character.create(name: "Luke", movie: movies.first)
|
0
lib/assets/.keep
Normal file
0
lib/assets/.keep
Normal file
0
lib/tasks/.keep
Normal file
0
lib/tasks/.keep
Normal file
0
log/.keep
Normal file
0
log/.keep
Normal file
67
public/404.html
Normal file
67
public/404.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>The page you were looking for doesn't exist (404)</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
.rails-default-error-page {
|
||||
background-color: #EFEFEF;
|
||||
color: #2E2F30;
|
||||
text-align: center;
|
||||
font-family: arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog {
|
||||
width: 95%;
|
||||
max-width: 33em;
|
||||
margin: 4em auto 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > div {
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #BBB;
|
||||
border-top: #B00100 solid 4px;
|
||||
border-top-left-radius: 9px;
|
||||
border-top-right-radius: 9px;
|
||||
background-color: white;
|
||||
padding: 7px 12% 0;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
|
||||
.rails-default-error-page h1 {
|
||||
font-size: 100%;
|
||||
color: #730E15;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > p {
|
||||
margin: 0 0 1em;
|
||||
padding: 1em;
|
||||
background-color: #F7F7F7;
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #999;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-top-color: #DADADA;
|
||||
color: #666;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="rails-default-error-page">
|
||||
<!-- This file lives in public/404.html -->
|
||||
<div class="dialog">
|
||||
<div>
|
||||
<h1>The page you were looking for doesn't exist.</h1>
|
||||
<p>You may have mistyped the address or the page may have moved.</p>
|
||||
</div>
|
||||
<p>If you are the application owner check the logs for more information.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
67
public/422.html
Normal file
67
public/422.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>The change you wanted was rejected (422)</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
.rails-default-error-page {
|
||||
background-color: #EFEFEF;
|
||||
color: #2E2F30;
|
||||
text-align: center;
|
||||
font-family: arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog {
|
||||
width: 95%;
|
||||
max-width: 33em;
|
||||
margin: 4em auto 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > div {
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #BBB;
|
||||
border-top: #B00100 solid 4px;
|
||||
border-top-left-radius: 9px;
|
||||
border-top-right-radius: 9px;
|
||||
background-color: white;
|
||||
padding: 7px 12% 0;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
|
||||
.rails-default-error-page h1 {
|
||||
font-size: 100%;
|
||||
color: #730E15;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > p {
|
||||
margin: 0 0 1em;
|
||||
padding: 1em;
|
||||
background-color: #F7F7F7;
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #999;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-top-color: #DADADA;
|
||||
color: #666;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="rails-default-error-page">
|
||||
<!-- This file lives in public/422.html -->
|
||||
<div class="dialog">
|
||||
<div>
|
||||
<h1>The change you wanted was rejected.</h1>
|
||||
<p>Maybe you tried to change something you didn't have access to.</p>
|
||||
</div>
|
||||
<p>If you are the application owner check the logs for more information.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
66
public/500.html
Normal file
66
public/500.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>We're sorry, but something went wrong (500)</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
.rails-default-error-page {
|
||||
background-color: #EFEFEF;
|
||||
color: #2E2F30;
|
||||
text-align: center;
|
||||
font-family: arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog {
|
||||
width: 95%;
|
||||
max-width: 33em;
|
||||
margin: 4em auto 0;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > div {
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #BBB;
|
||||
border-top: #B00100 solid 4px;
|
||||
border-top-left-radius: 9px;
|
||||
border-top-right-radius: 9px;
|
||||
background-color: white;
|
||||
padding: 7px 12% 0;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
|
||||
.rails-default-error-page h1 {
|
||||
font-size: 100%;
|
||||
color: #730E15;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.rails-default-error-page div.dialog > p {
|
||||
margin: 0 0 1em;
|
||||
padding: 1em;
|
||||
background-color: #F7F7F7;
|
||||
border: 1px solid #CCC;
|
||||
border-right-color: #999;
|
||||
border-left-color: #999;
|
||||
border-bottom-color: #999;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-top-color: #DADADA;
|
||||
color: #666;
|
||||
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="rails-default-error-page">
|
||||
<!-- This file lives in public/500.html -->
|
||||
<div class="dialog">
|
||||
<div>
|
||||
<h1>We're sorry, but something went wrong.</h1>
|
||||
</div>
|
||||
<p>If you are the application owner check the logs for more information.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
0
public/apple-touch-icon-precomposed.png
Normal file
0
public/apple-touch-icon-precomposed.png
Normal file
0
public/apple-touch-icon.png
Normal file
0
public/apple-touch-icon.png
Normal file
0
public/cached-images/.placeholder
Normal file
0
public/cached-images/.placeholder
Normal file
0
public/favicon.ico
Normal file
0
public/favicon.ico
Normal file
BIN
public/img/banner.png
Normal file
BIN
public/img/banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue