docs: document config/ and some directories in app/.

* Add README files to several directories in app/ giving a brief
  overview of some parts of Danbooru's architecture.
* Add documentation for files in config/.
This commit is contained in:
evazion
2021-06-26 22:47:54 -05:00
parent b9068b8a3e
commit 0563ca3001
28 changed files with 534 additions and 8 deletions

21
app/components/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Components
Components are used to encapsulate common UI widgets used throughout the site. This includes things like comments, tag
lists, post thumbnails, paginators, and other things used repeatedly throughout the site. Components encapsulate a
widget's HTML, CSS, and Javascript together so that all the code for a component is in a single place.
This uses the [ViewComponent](https://github.com/github/view_component) library.
Components are similar in concept to React components.
# See also
* [app/views](../views)
* [app/javascript](../javascript)
* [test/components](../../test/components)
# External links
* https://viewcomponent.org
* https://github.com/github/view_component
* https://github.blog/2020-12-15-encapsulating-ruby-on-rails-views/

164
app/controllers/README.md Normal file
View File

@@ -0,0 +1,164 @@
# Controllers
Controllers are the entry points to Danbooru. Every URL on the site corresponds to a controller action. When a request
for an URL is made, the corresponding controller action is called to handle the request.
Controllers follow a convention where, for example, the URL <https://danbooru.donmai.us/posts/1234> is handled by the
`#show` method inside the `PostsController` living at [app/controllers/posts_controller.rb](posts_controller.rb).
<https://danbooru.donmai.us/posts?tags=touhou> is handled by the `#index` method in the PostsController. The HTML
template for the response lives at [app/views/posts/index.html.erb](../views/posts/index.html.erb). See below for more
examples.
Controllers are responsible for taking the URL parameters, checking whether the user is authorized to perform the
action, actually performing the action, then returning the response. Most controllers simply fetch or update a model,
then render an HTML template from [app/views](../views) in response.
# Example
A standard controller looks something like this:
```ruby
class BansController < ApplicationController
def new
@ban = authorize Ban.new(permitted_attributes(Ban))
respond_with(@ban)
end
def edit
@ban = authorize Ban.find(params[:id])
respond_with(@ban)
end
def index
@bans = authorize Ban.paginated_search(params, count_pages: true)
respond_with(@bans)
end
def show
@ban = authorize Ban.find(params[:id])
respond_with(@ban)
end
def create
@ban = authorize Ban.new(banner: CurrentUser.user, **permitted_attributes(Ban))
@ban.save
respond_with(@ban, location: bans_path)
end
def update
@ban = authorize Ban.find(params[:id])
@ban.update(permitted_attributes(@ban))
respond_with(@ban)
end
def destroy
@ban = authorize Ban.find(params[:id])
@ban.destroy
respond_with(@ban)
end
end
```
# Routes
Each controller action above corresponds to an URL:
| Controller Action | URL | Route | Route Helper | View |
|------------------------|---------------------------------------------|---------------------|---------------------|-------------------------------|
| BansController#new | https://danbooru.donmai.us/bans/new | GET /bans/new | new_ban_path | app/views/bans/new.html.erb |
| BansController#edit | https://danbooru.donmai.us/bans/1234/edit | GET /bans/:id/edit | edit_ban_path(@ban) | app/views/bans/edit.html.erb |
| BansController#index | https://danbooru.donmai.us/bans | GET /bans | bans_path | app/views/bans/index.html.erb |
| BansController#show | https://danbooru.donmai.us/bans/1234 | GET /bans/:id | ban_path(@ban) | app/views/bans/show.html.erb |
| BansController#create | POST https://danbooru.donmai.us/bans | POST /bans | | |
| BansController#update | PUT https://danbooru.donmai.us/bans/1234 | PUT /bans/:id | | |
| BansController#destroy | DELETE https://danbooru.donmai.us/bans/1234 | DELETE /bans/:id | | |
These routes are defined in [config/routes.rb](../config/routes.rb).
# Authorization
Most permission checks for whether a user has permission to do something happen inside controllers, using `authorize`
calls.
The `authorize` method comes from the [Pundit](https://github.com/varvet/pundit) framework. This method checks whether
the current user is authorized to perform the current action. If not, it raises a `Pundit::NotAuthorizedError`, which is
caught in the `ApplicationController`.
The actual authorization logic for these calls lives in [app/policies](../policies). They follow a convention where the
authorization logic for the `BansController#create` action lives in `BanPolicy#create?`, which lives in
[app/policies/ban_policy.rb](../policies/ban_policy.rb). The call to `authorize` in the controller simply finds and
calls the ban policy.
The `#create`, `#new`, and `#update` actions also use `permitted_attributes` to check that the user is allowed to update
the model attributes they're trying to update. This also comes from the Pundit framework. See the
`permitted_attributes_for_create` and `permitted_attributes_for_update` methods in [app/policies](../policies).
# Responses
Controllers use `respond_with(@post)` to generate a response. This comes from the [Responders](https://github.com/heartcombo/responders)
gem. `respond_with` does the following:
* Detects whether the user wants an HTML, JSON, or XML response.
* Renders an HTML template from [app/views](../views) for HTML requests.
* Renders JSON or XML for API requests.
* Handles universal URL parameters, like `only` or `includes`.
* Handles universal behavior, like returning 200 OK for successful responses, or returning an error if trying
to save a model with validation errors.
# HTML Responses
The HTML templates for controller actions live in [app/views](../views). For example, the template for
PostsController#show, which corresponds to https://danbooru.donmai.us/posts/1234, lives in
[app/views/posts/show.html.erb](../views/posts/show.html.erb).
Instance variables set by controllers are automatically inherited by views. For example, if a controller sets `@post`,
then the `@post` variable will be available in the view.
# API Responses
All URLs support JSON or XML responses. This is handled by `respond_with`.
The response format can be chosen in several ways. First, by adding a .json or .xml file extension:
* https://danbooru.donmai.us/posts.json
* https://danbooru.donmai.us/posts.xml
Second, by setting the `format` URL parameter:
* https://danbooru.donmai.us/posts?format=json
* https://danbooru.donmai.us/posts?format=xml
Third, by setting the `Accept` HTTP header:
```sh
curl -H "Accept: application/json" https://danbooru.donmai.us/posts
curl -H "Accept: application/xml" https://danbooru.donmai.us/posts
```
When generating API responses, `respond_with` uses the `api_attributes` method inside [app/policies](../policies) to
determine which attributes are visible to the current user.
# Application Controller
Global behavior that runs on every request lives inside the [ApplicationController](application_controller.rb). This
includes the following:
* Setting the current user based on their session cookies or API keys.
* Checking rate limits.
* Checking for IP bans.
* Adding certain HTTP headers.
* Handling exceptions.
# See also
* [app/models](../models)
* [app/policies](../policies)
* [app/views](../views)
* [config/routes.rb](../../config/routes.rb)
* [test/functional](../../test/functional)
# External links
* https://guides.rubyonrails.org/action_controller_overview.html
* https://github.com/heartcombo/responders
* https://github.com/varvet/pundit

25
app/helpers/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Helpers
This directory contains helper functions used by views. Helpers are used for simple common functions, such as linking to
users or formatting timestamps.
Helper functions are globals. If you see an unnamespaced function in a view, and it's not a Rails function, then it's
probably a helper defined here.
All helper functions defined in this directory are globally available to all views. They're not limited to single views.
For example, the functions in [posts_helper.rb](posts_helper.rb) are available to all views, not just to
[app/views/posts](../views/posts).
The use of helper functions should be minimized. Partials or components are preferred for more complex widgets, or for
things used in only one or two places. Helper functions should be limited to very simple things used in nearly all
views.
# See also
* [app/views](../views)
* [app/components](../components)
# External links
* https://api.rubyonrails.org/classes/ActionController/Helpers.html
* https://www.rubyguides.com/2020/01/rails-helpers/

58
app/jobs/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Jobs
This directory contains background jobs used by Danbooru. Jobs are used to
handle slow-running tasks that need to run in the background, such as processing
uploads or bulk update requests. They're also used for asynchronous tasks, such
as sending emails, that may temporarily fail but can be automatically retried
later.
Jobs use the Rails Active Job framework. Active Job is a common framework that
allows jobs to be run on different job runner backends.
In the production environment, jobs are run using the Delayed Job backend. Jobs
are stored in the database in the `delayed_job` table. Worker processes spawned
by `bin/delayed_job` poll the table for new jobs to work.
In the development environment, jobs are run with an in-process thread pool.
This will run jobs in the background, but will drop jobs when the server is
restarted.
There are two job queues, the `default` queue and the `bulk_update`. The
`bulk_update` queue handles bulk update requests. It has only one worker so that
bulk update requests are effectively processed sequentially. The `default` queue
handles everything else.
There is a very minimal admin dashboard for jobs at https://danbooru.donmai.us/delayed_jobs.
Danbooru also has periodic maintenance tasks that run in the background as cron
jobs. These are different from the jobs in this directory. See [app/logical/danbooru_maintenance.rb](../logical/danbooru_maintenance.rb).
# Usage
Start a pool of job workers:
```
RAILS_ENV=production bin/delayed_job --pool=default:8 --pool=bulk_update start
```
# Examples
Spawn a job to be worked in the background. It will be worked as soon as a
worker is available:
```ruby
DeleteFavoritesJob.perform_later(user)
```
# See also
* [app/logical/danbooru_maintenance.rb](../logical/danbooru_maintenance.rb)
* [app/controllers/delayed_jobs_controller.rb](../controllers/delayed_jobs_controller.rb)
* [config/initializers/delayed_jobs.rb](../../config/initializers/delayed_jobs.rb)
* [test/jobs](../../test/jobs)
# External links
* https://guides.rubyonrails.org/active_job_basics.html
* https://github.com/collectiveidea/delayed_job
* https://danbooru.donmai.us/delayed_jobs

12
app/logical/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Logical
This directory contains library code used through Danbooru. This includes things like defining API clients, dealing with
sources, parsing tag searches, storing and resizing images, and so on.
Many of the files here use the Service Object pattern. Instead of putting complex code in models or controllers, it goes
here, in plain old Ruby objects (POROs). This keeps models and controllers simpler, and keeps domain logic isolated and
independent from the database and the HTTP request cycle.
# External links
* https://www.codewithjason.com/rails-service-objects/

31
app/mailers/README.md Normal file
View File

@@ -0,0 +1,31 @@
# Mailers
This directory contains mailers for sending emails. Mailers are kind of like controllers, except for generating emails
instead of generating HTML.
The actual email templates live in [app/views/user_mailer](../views/user_mailer).
Emails are sent asynchronously using a background job. If sending the email fails, it will be retried later.
Sending emails requires a SMTP server to be configured in
[config/danbooru_local_config.rb](../../config/danbooru_local_config.rb). In production,
[Amazon SES](https://aws.amazon.com/ses/) is used to send emails.
Email templates can be previewed at http://localhost:3000/rails/mailers (assuming you're running `bin/rails server` on
port 3000, the default).
# Example
```ruby
UserMailer.welcome_user(@user).deliver_later
```
# See also
* [app/views/user_mailer](../views/user_mailer)
* [test/mailers/previews/user_mailer_preview.rb](../../test/mailers/previews/user_mailer_preview.rb)
# External links
* https://guides.rubyonrails.org/action_mailer_basics.html
* https://guides.rubyonrails.org/testing.html#testing-your-mailers