forked from e621ng/e621ng
Merge remote-tracking branch 'origin/master' into rails-7
This commit is contained in:
commit
dd3fb80b8c
@ -68,3 +68,10 @@
|
||||
# export DANBOORU_VERSION=
|
||||
# export DANBOORU_HOSTNAME=
|
||||
# export DANBOORU_IQDBS_SERVER=
|
||||
|
||||
#
|
||||
# Development Only
|
||||
#
|
||||
|
||||
# Start the integrated solargraph service from the compose file. Requires a rebuild when changed.
|
||||
# export COMPOSE_PROFILES=solargraph
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
.env.*
|
||||
.env
|
||||
.bundle
|
||||
.yardoc
|
||||
config/database.yml
|
||||
|
68
.rubocop.yml
68
.rubocop.yml
@ -4,8 +4,8 @@ require:
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
Exclude:
|
||||
- "bin/*"
|
||||
- "node_modules/**/*"
|
||||
- bin/*
|
||||
- node_modules/**/*
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
@ -13,23 +13,30 @@ Bundler/OrderedGems:
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
Enabled: false
|
||||
Layout/FirstArrayElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Layout/FirstHashElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Layout/LineLength:
|
||||
Enabled: false
|
||||
|
||||
Lint/InheritException:
|
||||
Enabled: false
|
||||
|
||||
Lint/RescueException:
|
||||
Enabled: false
|
||||
Lint/SymbolConversion:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
AllowedMethods:
|
||||
- class_methods
|
||||
- concerning
|
||||
- context
|
||||
- create_table
|
||||
- should
|
||||
Exclude:
|
||||
- config/routes.rb
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
@ -49,15 +56,28 @@ Metrics/PerceivedComplexity:
|
||||
Naming/PredicateName:
|
||||
Enabled: false
|
||||
|
||||
Rails/BulkChangeTable:
|
||||
Enabled: false
|
||||
|
||||
Rails/HasManyOrHasOneDependent:
|
||||
Enabled: false
|
||||
|
||||
Rails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
Rails/I18nLocaleTexts:
|
||||
Enabled: false
|
||||
|
||||
Rails/InverseOf:
|
||||
Enabled: false
|
||||
|
||||
Rails/Output:
|
||||
Exclude:
|
||||
- db/*.rb
|
||||
|
||||
Rails/ReversibleMigration:
|
||||
Enabled: false
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
@ -85,14 +105,17 @@ Style/FloatDivision:
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
Style/HashSyntax:
|
||||
EnforcedShorthandSyntax: never
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
Style/MutableConstant:
|
||||
Enabled: false
|
||||
|
||||
Style/NumericPredicate:
|
||||
Enabled: false
|
||||
EnforcedStyle: comparison
|
||||
|
||||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
@ -100,17 +123,14 @@ Style/PerlBackrefs:
|
||||
Style/QuotedSymbols:
|
||||
Enabled: false
|
||||
|
||||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
EnforcedStyle: double_quotes
|
||||
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Enabled: false
|
||||
Style/TrailingCommaInArguments:
|
||||
EnforcedStyleForMultiline: comma
|
||||
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
EnforcedStyleForMultiline: consistent_comma
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: consistent_comma
|
||||
|
@ -1,31 +1,12 @@
|
||||
---
|
||||
include:
|
||||
- "**/*.rb"
|
||||
exclude:
|
||||
- spec/**/*
|
||||
- test/**/*
|
||||
- vendor/**/*
|
||||
- ".bundle/**/*"
|
||||
require:
|
||||
- actioncable
|
||||
- actionmailer
|
||||
- actionpack
|
||||
- actionview
|
||||
- activejob
|
||||
- activemodel
|
||||
- activerecord
|
||||
- activestorage
|
||||
- activesupport
|
||||
domains: []
|
||||
- actioncable
|
||||
- actionmailer
|
||||
- actionpack
|
||||
- actionview
|
||||
- activejob
|
||||
- activemodel
|
||||
- activerecord
|
||||
- activestorage
|
||||
- activesupport
|
||||
reporters:
|
||||
- rubocop
|
||||
- require_not_found
|
||||
formatter:
|
||||
rubocop:
|
||||
cops: safe
|
||||
except: []
|
||||
only: []
|
||||
extra_args: []
|
||||
require_paths: []
|
||||
plugins: []
|
||||
max_files: 5000
|
||||
- rubocop
|
||||
|
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"solargraph.checkGemVersion": false,
|
||||
"solargraph.diagnostics": true,
|
||||
"solargraph.externalServer": {
|
||||
"host": "localhost",
|
||||
"port": 7658
|
||||
},
|
||||
"solargraph.formatting": true,
|
||||
"solargraph.transport": "external",
|
||||
}
|
10
Dockerfile
10
Dockerfile
@ -26,5 +26,15 @@ RUN gem install bundler:2.3.12 && \
|
||||
RUN wget -O /usr/bin/shoreman https://github.com/chrismytton/shoreman/raw/master/shoreman.sh \
|
||||
&& chmod +x /usr/bin/shoreman
|
||||
|
||||
|
||||
# Only setup solargraph stuff when the profile is selected
|
||||
ARG COMPOSE_PROFILES
|
||||
RUN if [[ $COMPOSE_PROFILES == *"solargraph"* ]]; then \
|
||||
solargraph download-core && bundle exec yard gems && solargraph bundle; \
|
||||
fi
|
||||
|
||||
# Stop bin/rails console from offering autocomplete
|
||||
RUN echo "IRB.conf[:USE_AUTOCOMPLETE] = false" > ~/.irbrc
|
||||
|
||||
WORKDIR /app
|
||||
CMD [ "shoreman" ]
|
||||
|
6
Gemfile
6
Gemfile
@ -53,6 +53,12 @@ group :development, :test do
|
||||
gem 'puma'
|
||||
end
|
||||
|
||||
group :docker do
|
||||
gem "rubocop", require: false
|
||||
gem "rubocop-rails", require: false
|
||||
gem "solargraph", require: false
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "shoulda-context"
|
||||
gem "shoulda-matchers"
|
||||
|
56
Gemfile.lock
56
Gemfile.lock
@ -85,7 +85,10 @@ GEM
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
backport (1.2.0)
|
||||
bcrypt (3.1.18)
|
||||
benchmark (0.2.0)
|
||||
bootsnap (1.13.0)
|
||||
msgpack (~> 1.2)
|
||||
brpoplpush-redis_script (0.1.2)
|
||||
@ -118,6 +121,7 @@ GEM
|
||||
activesupport (>= 5.0)
|
||||
request_store (>= 1.0)
|
||||
ruby2_keywords
|
||||
e2mmap (0.1.0)
|
||||
elasticsearch (7.17.1)
|
||||
elasticsearch-api (= 7.17.1)
|
||||
elasticsearch-transport (= 7.17.1)
|
||||
@ -174,9 +178,14 @@ GEM
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jaro_winkler (1.5.4)
|
||||
json (2.6.2)
|
||||
jsonapi-renderer (0.2.2)
|
||||
kgio (2.11.4)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
@ -217,6 +226,9 @@ GEM
|
||||
nokogiri (1.13.8)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
parallel (1.22.1)
|
||||
parser (3.1.2.1)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.4.3)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
@ -259,6 +271,7 @@ GEM
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
raindrops (0.20.0)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.11.2)
|
||||
@ -267,6 +280,7 @@ GEM
|
||||
recaptcha (5.12.3)
|
||||
json
|
||||
redis (4.8.0)
|
||||
regexp_parser (2.6.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
resolv (0.2.1)
|
||||
@ -285,7 +299,26 @@ GEM
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
reverse_markdown (2.1.1)
|
||||
nokogiri
|
||||
rexml (3.2.5)
|
||||
rubocop (1.37.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.2.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.22.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.23.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rails (2.17.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-vips (2.1.4)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
@ -308,9 +341,25 @@ GEM
|
||||
simple_form (5.1.0)
|
||||
actionpack (>= 5.2)
|
||||
activemodel (>= 5.2)
|
||||
solargraph (0.47.2)
|
||||
backport (~> 1.2)
|
||||
benchmark
|
||||
bundler (>= 1.17.2)
|
||||
diff-lcs (~> 1.4)
|
||||
e2mmap
|
||||
jaro_winkler (~> 1.5)
|
||||
kramdown (~> 2.3)
|
||||
kramdown-parser-gfm (~> 1.1)
|
||||
parser (~> 3.0)
|
||||
reverse_markdown (>= 1.0.5, < 3)
|
||||
rubocop (>= 0.52)
|
||||
thor (~> 1.0)
|
||||
tilt (~> 2.0)
|
||||
yard (~> 0.9, >= 0.9.24)
|
||||
streamio-ffmpeg (3.0.2)
|
||||
multi_json (~> 1.8)
|
||||
thor (1.2.1)
|
||||
tilt (2.0.11)
|
||||
timecop (0.9.5)
|
||||
timeout (0.3.0)
|
||||
tzinfo (2.0.5)
|
||||
@ -318,6 +367,7 @@ GEM
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.3.0)
|
||||
unicorn (6.1.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
@ -333,11 +383,14 @@ GEM
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.7.0)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
whenever (1.0.0)
|
||||
chronic (>= 0.6.3)
|
||||
yard (0.9.28)
|
||||
webrick (~> 1.7.0)
|
||||
zeitwerk (2.6.1)
|
||||
|
||||
PLATFORMS
|
||||
@ -377,6 +430,8 @@ DEPENDENCIES
|
||||
resolv
|
||||
responders
|
||||
retriable
|
||||
rubocop
|
||||
rubocop-rails
|
||||
ruby-vips
|
||||
sanitize
|
||||
shoulda-context
|
||||
@ -384,6 +439,7 @@ DEPENDENCIES
|
||||
sidekiq
|
||||
sidekiq-unique-jobs
|
||||
simple_form
|
||||
solargraph
|
||||
streamio-ffmpeg
|
||||
timecop
|
||||
unicorn
|
||||
|
24
README.md
24
README.md
@ -15,22 +15,30 @@ To mitigate this you can install a WSL distribution and clone the project inside
|
||||
|
||||
#### Installation
|
||||
1. Download and install the prerequisites.
|
||||
2. Clone the repo with `git clone https://github.com/zwagoth/e621ng.git`.
|
||||
3. `cd` into the repo.
|
||||
4. Run the following commands:
|
||||
1. Clone the repo with `git clone https://github.com/zwagoth/e621ng.git`.
|
||||
1. `cd` into the repo.
|
||||
1. Copy the sample environment file with `cp .env.sample .env`.
|
||||
1. Uncomment the `COMPOSE_PROFILES` variable if you wish to use solargraph. Doesn't work on Windows without WSL.
|
||||
1. Run the following commands:
|
||||
```
|
||||
docker-compose run -e DANBOORU_DISABLE_THROTTLES=true -e SEED_POST_COUNT=100 e621 /app/bin/setup
|
||||
docker-compose up
|
||||
```
|
||||
After running the commands once only `docker-compose up` is needed to bring up the containers.
|
||||
5. This would be a good time to rewatch your favorite TV series installment, cook & have breakfast/lunch/dinner, walk the dog, clean your room, etc.<br>
|
||||
1. This would be a good time to rewatch your favorite TV series installment, cook & have breakfast/lunch/dinner, walk the dog, clean your room, etc.<br>
|
||||
By the time you get back the install will surely have completed.<sup>1</sup>
|
||||
6. To confirm the installation worked, open the web browser of your choice and enter `http://localhost:3000` into the address bar and see if the website loads correctly. An admin account has been created automatically, the username and password are `admin` and `e621test` respectively.
|
||||
1. To confirm the installation worked, open the web browser of your choice and enter `http://localhost:3000` into the address bar and see if the website loads correctly. An admin account has been created automatically, the username and password are `admin` and `e621test` respectively.
|
||||
|
||||
Note: When gems or js packages were updated you need to execute `docker-compose build` to reflect them in the container.
|
||||
|
||||
<sub><sup>1</sup> If the install did not finish by the time an activity is complete please select another activity to avoid crippling boredom.</sub>
|
||||
|
||||
#### Useful docker services
|
||||
|
||||
`docker-compose run --rm tests` to execute the test suite.
|
||||
|
||||
`docker-compose run --rm rubocop` to run the linter. Run it against changed files only, there are too many existing violations at the moment.
|
||||
|
||||
#### Development Database
|
||||
|
||||
The postgres server accepts outside connections which you can use to connect with a local client. Use `localhost:34517` to connect to a database named `danbooru2` with the user `danbooru`. Leave the password blank, anything will work.
|
||||
@ -66,9 +74,3 @@ debug your Nginx configuration file.
|
||||
### IQDB Service
|
||||
|
||||
IQDB integration is delegated to the [IQDBS service](https://github.com/zwagoth/iqdbs).
|
||||
|
||||
### Cropped Thumbnails
|
||||
|
||||
There's optional support for cropped thumbnails. This relies on installing
|
||||
`libvips-8.6` or higher and setting `Danbooru.config.enable_image_cropping?`
|
||||
to true.
|
||||
|
@ -28,6 +28,8 @@ ORDER BY u1.id DESC, u2.last_logged_in_at DESC;")
|
||||
|
||||
def update
|
||||
@user = User.find(params[:id])
|
||||
@user.validate_email_format = true
|
||||
@user.skip_email_blank_check = true
|
||||
@user.update!(user_params)
|
||||
if @user.saved_change_to_profile_about || @user.saved_change_to_profile_artinfo
|
||||
ModAction.log(:user_text_change, { user_id: @user.id })
|
||||
@ -37,8 +39,6 @@ ORDER BY u1.id DESC, u2.last_logged_in_at DESC;")
|
||||
end
|
||||
@user.mark_verified! if params[:user][:verified] == 'true'
|
||||
@user.mark_unverified! if params[:user][:verified] == 'false'
|
||||
params[:user][:is_upgrade] = true
|
||||
params[:user][:skip_dmail] = true
|
||||
@user.promote_to!(params[:user][:level], params[:user])
|
||||
|
||||
old_username = @user.name
|
||||
|
@ -32,12 +32,6 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
protected
|
||||
|
||||
def self.rescue_with(*klasses, status: 500)
|
||||
rescue_from *klasses do |exception|
|
||||
render_error_page(status, exception)
|
||||
end
|
||||
end
|
||||
|
||||
def api_check
|
||||
if !CurrentUser.is_anonymous? && !request.get? && !request.head?
|
||||
throttled = CurrentUser.user.token_bucket.throttled?
|
||||
@ -55,6 +49,9 @@ class ApplicationController < ActionController::Base
|
||||
def rescue_exception(exception)
|
||||
@exception = exception
|
||||
|
||||
# If InvalidAuthenticityToken was raised, CurrentUser isn't set so we have to do it here manually.
|
||||
CurrentUser.user ||= User.anonymous
|
||||
|
||||
if Rails.env.test? && ENV["DEBUG"]
|
||||
puts "---"
|
||||
STDERR.puts("#{exception.class} exception thrown: #{exception.message}")
|
||||
@ -120,9 +117,8 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def render_expected_error(status, message, format: request.format.symbol)
|
||||
format = :html unless format.in?(%i[html json atom])
|
||||
layout = CurrentUser.user.present? ? "default" : "blank"
|
||||
@message = message
|
||||
render "static/error", layout: layout, status: status, formats: format
|
||||
render "static/error", status: status, formats: format
|
||||
end
|
||||
|
||||
def render_error_page(status, exception, message: exception.message, format: request.format.symbol)
|
||||
@ -132,17 +128,14 @@ class ApplicationController < ActionController::Base
|
||||
@backtrace = Rails.backtrace_cleaner.clean(@exception.backtrace)
|
||||
format = :html unless format.in?(%i[html json atom])
|
||||
|
||||
# if InvalidAuthenticityToken was raised, CurrentUser isn't set so we have to use the blank layout.
|
||||
layout = CurrentUser.user.present? ? "default" : "blank"
|
||||
|
||||
if !CurrentUser.user&.try(:is_janitor?) && message == exception.message
|
||||
if !CurrentUser.user.is_janitor? && message == exception.message
|
||||
@message = "An unexpected error occurred."
|
||||
end
|
||||
|
||||
DanbooruLogger.log(@exception, expected: @expected)
|
||||
log = ExceptionLog.add(exception, CurrentUser.id, request) if !@expected
|
||||
@log_code = log&.code
|
||||
render "static/error", layout: layout, status: status, formats: format
|
||||
render "static/error", status: status, formats: format
|
||||
end
|
||||
|
||||
def access_denied(exception = nil)
|
||||
|
@ -8,19 +8,19 @@ class CommentVotesController < ApplicationController
|
||||
def create
|
||||
@comment = Comment.find(params[:comment_id])
|
||||
@comment_vote = VoteManager.comment_vote!(comment: @comment, user: CurrentUser.user, score: params[:score])
|
||||
if @comment_vote == :need_unvote
|
||||
if @comment_vote == :need_unvote && params[:no_unvote] != "true"
|
||||
VoteManager.comment_unvote!(comment: @comment, user: CurrentUser.user)
|
||||
end
|
||||
@comment.reload
|
||||
render json: {score: @comment.score, our_score: @comment_vote != :need_unvote ? @comment_vote.score : 0}
|
||||
rescue CommentVote::Error, ActiveRecord::RecordInvalid => x
|
||||
rescue UserVote::Error, ActiveRecord::RecordInvalid => x
|
||||
render_expected_error(422, x)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@comment = Comment.find(params[:comment_id])
|
||||
VoteManager.comment_unvote!(comment: @comment, user: CurrentUser.user)
|
||||
rescue CommentVote::Error => x
|
||||
rescue UserVote::Error => x
|
||||
render_expected_error(422, x)
|
||||
end
|
||||
|
||||
|
43
app/controllers/mascots_controller.rb
Normal file
43
app/controllers/mascots_controller.rb
Normal file
@ -0,0 +1,43 @@
|
||||
class MascotsController < ApplicationController
|
||||
respond_to :html, :json
|
||||
before_action :admin_only, except: [:index]
|
||||
|
||||
def index
|
||||
@mascots = Mascot.search(search_params).paginate(params[:page], limit: 75)
|
||||
respond_with(@mascots)
|
||||
end
|
||||
|
||||
def new
|
||||
@mascot = Mascot.new
|
||||
end
|
||||
|
||||
def create
|
||||
@mascot = Mascot.create(mascot_params.merge(creator: CurrentUser.user))
|
||||
ModAction.log(:mascot_create, { id: @mascot.id }) if @mascot.valid?
|
||||
respond_with(@mascot, location: mascots_path)
|
||||
end
|
||||
|
||||
def edit
|
||||
@mascot = Mascot.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@mascot = Mascot.find(params[:id])
|
||||
@mascot.update(mascot_params)
|
||||
ModAction.log(:mascot_update, { id: @mascot.id }) if @mascot.valid?
|
||||
respond_with(@mascot, location: mascots_path)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@mascot = Mascot.find(params[:id])
|
||||
@mascot.destroy
|
||||
ModAction.log(:mascot_delete, { id: @mascot.id })
|
||||
respond_with(@mascot)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mascot_params
|
||||
params.fetch(:mascot, {}).permit(%i[mascot_file display_name background_color artist_url artist_name safe_mode_only active])
|
||||
end
|
||||
end
|
@ -4,7 +4,7 @@ class PostVersionsController < ApplicationController
|
||||
respond_to :js, only: [:undo]
|
||||
|
||||
def index
|
||||
@post_versions = PostVersion.__elasticsearch__.search(PostVersion.build_query(search_params)).paginate(params[:page], :limit => params[:limit], :search_count => params[:search], includes: [:updater, post: [:versions]])
|
||||
@post_versions = PostVersion.__elasticsearch__.search(PostVersion.build_query(search_params)).paginate(params[:page], limit: params[:limit], max_count: 10_000, search_count: params[:search], includes: [:updater, post: [:versions]])
|
||||
respond_with(@post_versions)
|
||||
end
|
||||
|
||||
|
@ -10,14 +10,14 @@ class PostVotesController < ApplicationController
|
||||
VoteManager.unvote!(post: @post, user: CurrentUser.user)
|
||||
end
|
||||
render json: {score: @post.score, up: @post.up_score, down: @post.down_score, our_score: @post_vote != :need_unvote ? @post_vote.score : 0}
|
||||
rescue PostVote::Error, ActiveRecord::RecordInvalid => x
|
||||
rescue UserVote::Error, ActiveRecord::RecordInvalid => x
|
||||
render_expected_error(422, x)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@post = Post.find(params[:post_id])
|
||||
VoteManager.unvote!(post: @post, user: CurrentUser.user)
|
||||
rescue PostVote::Error => x
|
||||
rescue UserVote::Error => x
|
||||
render_expected_error(422, x)
|
||||
end
|
||||
|
||||
|
@ -10,6 +10,7 @@ class TakedownsController < ApplicationController
|
||||
def destroy
|
||||
@takedown = Takedown.find(params[:id])
|
||||
@takedown.destroy
|
||||
ModAction.log(:takedown_delete, { takedown_id: @takedown.id })
|
||||
respond_with(@takedown)
|
||||
end
|
||||
|
||||
|
@ -19,12 +19,7 @@ class UsersController < ApplicationController
|
||||
|
||||
def index
|
||||
if params[:name].present?
|
||||
@user = User.find_by_name(params[:name])
|
||||
if @user.nil?
|
||||
raise "No user found with name: #{params[:name]}"
|
||||
else
|
||||
redirect_to user_path(@user)
|
||||
end
|
||||
redirect_to user_path(id: params[:name])
|
||||
else
|
||||
@users = User.search(search_params).includes(:user_status).paginate(params[:page], limit: params[:limit], search_count: params[:search])
|
||||
respond_with(@users) do |format|
|
||||
@ -63,6 +58,7 @@ class UsersController < ApplicationController
|
||||
raise User::PrivilegeError.new("Signups are disabled") unless Danbooru.config.enable_signups?
|
||||
User.transaction do
|
||||
@user = User.new(user_params(:create).merge({last_ip_addr: request.remote_ip}))
|
||||
@user.validate_email_format = true
|
||||
@user.email_verification_key = '1' if Danbooru.config.enable_email_verification?
|
||||
if !Danbooru.config.enable_recaptcha? || verify_recaptcha(model: @user)
|
||||
@user.save
|
||||
@ -92,6 +88,7 @@ class UsersController < ApplicationController
|
||||
|
||||
def update
|
||||
@user = User.find(CurrentUser.id)
|
||||
@user.validate_email_format = true
|
||||
check_privilege(@user)
|
||||
@user.update(user_params(:update))
|
||||
if @user.errors.any?
|
||||
|
@ -23,6 +23,8 @@ class ModActionDecorator < ApplicationDecorator
|
||||
### Takedowns ###
|
||||
when "takedown_process"
|
||||
"Completed takedown ##{vals['takedown_id']}"
|
||||
when "takedown_delete"
|
||||
"Deleted takedown ##{vals['takedown_id']}"
|
||||
|
||||
### IP Ban ###
|
||||
when "ip_ban_create"
|
||||
@ -292,6 +294,14 @@ class ModActionDecorator < ApplicationDecorator
|
||||
when "wiki_page_rename"
|
||||
"Renamed wiki page ([[#{vals['old_title']}]] → [[#{vals['new_title']}]])"
|
||||
|
||||
### Mascots ###
|
||||
when "mascot_create"
|
||||
"Created mascot ##{vals['id']}"
|
||||
when "mascot_update"
|
||||
"Updated mascot ##{vals['id']}"
|
||||
when "mascot_delete"
|
||||
"Deleted mascot ##{vals['id']}"
|
||||
|
||||
when "bulk_revert"
|
||||
"Processed bulk revert for #{vals['constraints']} by #{user}"
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
class PaginatedDecorator < Draper::CollectionDecorator
|
||||
delegate :current_page, :total_pages, :is_first_page?, :is_last_page?, :sequential_paginator_mode, :records, :total_count
|
||||
delegate :current_page, :total_pages, :is_first_page?, :is_last_page?, :sequential_paginator_mode, :max_numbered_pages, :records, :total_count
|
||||
end
|
||||
|
@ -116,14 +116,6 @@ module ApplicationHelper
|
||||
content_tag(:time, content || datetime, :datetime => datetime, :title => time.to_formatted_s)
|
||||
end
|
||||
|
||||
def humanized_duration(from, to)
|
||||
duration = distance_of_time_in_words(from, to)
|
||||
datetime = from.iso8601 + "/" + to.iso8601
|
||||
title = "#{from.strftime("%Y-%m-%d %H:%M")} to #{to.strftime("%Y-%m-%d %H:%M")}"
|
||||
|
||||
raw content_tag(:time, duration, datetime: datetime, title: title)
|
||||
end
|
||||
|
||||
def time_ago_in_words_tagged(time, compact: false)
|
||||
if time.past?
|
||||
text = time_ago_in_words(time) + " ago"
|
||||
|
@ -3,7 +3,7 @@ module PaginationHelper
|
||||
html = '<div class="paginator"><menu>'
|
||||
|
||||
if records.respond_to?(:any?) && records.any?
|
||||
if params[:page] =~ /[ab]/ && !records.is_first_page?
|
||||
if !records.is_first_page?
|
||||
html << '<li>' + link_to("< Previous", nav_params_for("a#{records[0].id}"), rel: "prev", id: "paginator-prev", "data-shortcut": "a left") + '</li>'
|
||||
end
|
||||
|
||||
@ -17,7 +17,7 @@ module PaginationHelper
|
||||
end
|
||||
|
||||
def use_sequential_paginator?(records)
|
||||
params[:page] =~ /\A[ab]\d+\z/ || records.current_page >= Danbooru.config.max_numbered_pages
|
||||
params[:page] =~ /\A[ab]\d+\z/ || records.current_page >= records.max_numbered_pages
|
||||
end
|
||||
|
||||
def numbered_paginator(records, switch_to_sequential = true)
|
||||
@ -36,35 +36,35 @@ module PaginationHelper
|
||||
|
||||
if records.total_pages <= (window * 2) + 5
|
||||
1.upto(records.total_pages) do |page|
|
||||
html << numbered_paginator_item(page, records.current_page)
|
||||
html << numbered_paginator_item(page, records)
|
||||
end
|
||||
|
||||
elsif records.current_page <= window + 2
|
||||
1.upto(records.current_page + window) do |page|
|
||||
html << numbered_paginator_item(page, records.current_page)
|
||||
html << numbered_paginator_item(page, records)
|
||||
end
|
||||
html << numbered_paginator_item("...", records.current_page)
|
||||
html << numbered_paginator_final_item(records.total_pages, records.current_page)
|
||||
html << numbered_paginator_item("...", records)
|
||||
html << numbered_paginator_final_item(records)
|
||||
elsif records.current_page >= records.total_pages - (window + 1)
|
||||
html << numbered_paginator_item(1, records.current_page)
|
||||
html << numbered_paginator_item("...", records.current_page)
|
||||
html << numbered_paginator_item(1, records)
|
||||
html << numbered_paginator_item("...", records)
|
||||
(records.current_page - window).upto(records.total_pages) do |page|
|
||||
html << numbered_paginator_item(page, records.current_page)
|
||||
html << numbered_paginator_item(page, records)
|
||||
end
|
||||
else
|
||||
html << numbered_paginator_item(1, records.current_page)
|
||||
html << numbered_paginator_item("...", records.current_page)
|
||||
html << numbered_paginator_item(1, records)
|
||||
html << numbered_paginator_item("...", records)
|
||||
if records.size > 0
|
||||
right_window = records.current_page + window
|
||||
else
|
||||
right_window = records.current_page
|
||||
end
|
||||
(records.current_page - window).upto(right_window) do |page|
|
||||
html << numbered_paginator_item(page, records.current_page)
|
||||
html << numbered_paginator_item(page, records)
|
||||
end
|
||||
if records.size > 0
|
||||
html << numbered_paginator_item("...", records.current_page)
|
||||
html << numbered_paginator_final_item(records.total_pages, records.current_page)
|
||||
html << numbered_paginator_item("...", records)
|
||||
html << numbered_paginator_final_item(records)
|
||||
end
|
||||
end
|
||||
|
||||
@ -78,23 +78,23 @@ module PaginationHelper
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def numbered_paginator_final_item(total_pages, current_page)
|
||||
if total_pages <= Danbooru.config.max_numbered_pages
|
||||
numbered_paginator_item(total_pages, current_page)
|
||||
def numbered_paginator_final_item(records)
|
||||
if records.total_pages <= records.max_numbered_pages
|
||||
numbered_paginator_item(records.total_pages, records)
|
||||
else
|
||||
numbered_paginator_item(Danbooru.config.max_numbered_pages, current_page)
|
||||
numbered_paginator_item(records.max_numbered_pages, records)
|
||||
end
|
||||
end
|
||||
|
||||
def numbered_paginator_item(page, current_page)
|
||||
return "" if page.to_i > Danbooru.config.max_numbered_pages
|
||||
def numbered_paginator_item(page, records)
|
||||
return "" if page.to_i > records.max_numbered_pages
|
||||
|
||||
html = []
|
||||
if page == "..."
|
||||
html << "<li class='more'>"
|
||||
html << content_tag(:i, nil, class: "fas fa-ellipsis-h")
|
||||
html << "</li>"
|
||||
elsif page == current_page
|
||||
elsif page == records.current_page
|
||||
html << "<li class='current-page'>"
|
||||
html << '<span>' + page.to_s + '</span>'
|
||||
html << "</li>"
|
||||
|
@ -22,7 +22,7 @@ export default {
|
||||
watch: {
|
||||
addToList(value) {
|
||||
const maxEntries = 50;
|
||||
const entries = new Set([value, ...this.currentEntries()]);
|
||||
const entries = new Set([value.trim(), ...this.currentEntries()]);
|
||||
LS.putObject(`autocomplete-${this.listId}`, [...entries].slice(0, maxEntries));
|
||||
}
|
||||
},
|
||||
|
@ -4,28 +4,24 @@ const Mascots = {
|
||||
current: 0
|
||||
};
|
||||
|
||||
function showMascot(cur) {
|
||||
const mascots = window.mascots;
|
||||
function showMascot(mascot) {
|
||||
$('body').css("background-image", "url(" + mascot.background_url + ")");
|
||||
$('body').css("background-color", mascot.background_color);
|
||||
$('.mascotbox').css("background-image", "url(" + mascot.background_url + ")");
|
||||
$('.mascotbox').css("background-color", mascot.background_color);
|
||||
|
||||
const blurred = mascots[cur][0].substr(0, mascots[cur][0].lastIndexOf(".")) + "_blur" + mascots[cur][0].slice(mascots[cur][0].lastIndexOf("."));
|
||||
|
||||
$('body').css("background-image", "url(" + mascots[cur][0] + ")");
|
||||
$('body').css("background-color", mascots[cur][1]);
|
||||
$('.mascotbox').css("background-image", "url(" + blurred + ")");
|
||||
$('.mascotbox').css("background-color", mascots[cur][1]);
|
||||
|
||||
if (mascots[cur][2])
|
||||
$('#mascot_artist').html("Mascot by " + mascots[cur][2]);
|
||||
else
|
||||
$('#mascot_artist').html(" ");
|
||||
const artistLink = $("<span>").text("Mascot by ").append($("<a>").text(mascot.artist_name).attr("href", mascot.artist_url));
|
||||
$("#mascot_artist").empty().append(artistLink);
|
||||
}
|
||||
|
||||
function changeMascot() {
|
||||
const mascots = window.mascots;
|
||||
|
||||
Mascots.current += 1;
|
||||
Mascots.current = Mascots.current % mascots.length;
|
||||
showMascot(Mascots.current);
|
||||
const availableMascotIds = Object.keys(mascots);
|
||||
const currentMascotIndex = availableMascotIds.indexOf(Mascots.current);
|
||||
|
||||
Mascots.current = availableMascotIds[(currentMascotIndex + 1) % availableMascotIds.length];
|
||||
showMascot(mascots[Mascots.current]);
|
||||
|
||||
LS.put('mascot', Mascots.current);
|
||||
}
|
||||
@ -33,10 +29,13 @@ function changeMascot() {
|
||||
function initMascots() {
|
||||
$('#change-mascot').on('click', changeMascot);
|
||||
const mascots = window.mascots;
|
||||
Mascots.current = parseInt(LS.get("mascot"));
|
||||
if (isNaN(Mascots.current) || Mascots.current < 0 || Mascots.current >= mascots.length)
|
||||
Mascots.current = Math.floor(Math.random() * mascots.length);
|
||||
showMascot(Mascots.current);
|
||||
Mascots.current = LS.get("mascot");
|
||||
if (!mascots[Mascots.current]) {
|
||||
const availableMascotIds = Object.keys(mascots);
|
||||
const mascotIndex = Math.floor(Math.random() * availableMascotIds.length);
|
||||
Mascots.current = availableMascotIds[mascotIndex];
|
||||
}
|
||||
showMascot(mascots[Mascots.current]);
|
||||
}
|
||||
|
||||
$(function () {
|
||||
|
@ -3,28 +3,6 @@
|
||||
width: 15em; // Match width to that of the
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0% {
|
||||
transform:scale(1);
|
||||
}
|
||||
50% {
|
||||
transform:scale(1.3);
|
||||
}
|
||||
100% {
|
||||
transform:scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sharpen {
|
||||
from {
|
||||
filter: blur(8px);
|
||||
}
|
||||
|
||||
to {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
article.post-preview {
|
||||
box-sizing: border-box;
|
||||
height: 154px;
|
||||
|
@ -45,9 +45,7 @@ module Danbooru
|
||||
end
|
||||
|
||||
# taken from kaminari (https://github.com/amatsuda/kaminari)
|
||||
def total_count
|
||||
return option_for(:count) if option_for(:count)
|
||||
|
||||
def real_count
|
||||
c = except(:offset, :limit, :order)
|
||||
c = c.reorder(nil)
|
||||
c = c.count
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Danbooru
|
||||
module Paginator
|
||||
module BaseExtension
|
||||
def paginate_base(page, options)
|
||||
def paginate_base(page, options = {})
|
||||
@paginator_options = options
|
||||
|
||||
if use_numbered_paginator?(page)
|
||||
@ -16,8 +16,16 @@ module Danbooru
|
||||
end
|
||||
|
||||
def validate_numbered_page(page)
|
||||
return if page.to_i <= Danbooru.config.max_numbered_pages
|
||||
raise Danbooru::Paginator::PaginationError, "You cannot go beyond page #{Danbooru.config.max_numbered_pages}. Please narrow your search terms."
|
||||
return if page.to_i <= max_numbered_pages
|
||||
raise Danbooru::Paginator::PaginationError, "You cannot go beyond page #{max_numbered_pages}. Please narrow your search terms."
|
||||
end
|
||||
|
||||
def max_numbered_pages
|
||||
if @paginator_options[:max_count]
|
||||
[Danbooru.config.max_numbered_pages, @paginator_options[:max_count] / records_per_page].min
|
||||
else
|
||||
Danbooru.config.max_numbered_pages
|
||||
end
|
||||
end
|
||||
|
||||
def use_numbered_paginator?(page)
|
||||
@ -39,7 +47,8 @@ module Danbooru
|
||||
end
|
||||
|
||||
def records_per_page
|
||||
option_for(:limit).to_i
|
||||
limit = @paginator_options.try(:[], :limit) || Danbooru.config.posts_per_page
|
||||
[limit.to_i, 320].min
|
||||
end
|
||||
|
||||
# When paginating large tables, we want to avoid doing an expensive count query
|
||||
@ -48,24 +57,11 @@ module Danbooru
|
||||
# exist, then assume we're doing a search and don't override the default count
|
||||
# behavior. Otherwise, just return some large number so the paginator skips the
|
||||
# count.
|
||||
def option_for(key)
|
||||
case key
|
||||
when :limit
|
||||
limit = @paginator_options.try(:[], :limit) || Danbooru.config.posts_per_page
|
||||
if limit.to_i > 320
|
||||
limit = 320
|
||||
end
|
||||
limit
|
||||
def total_count
|
||||
return 1_000_000 if @paginator_options.key?(:search_count) && @paginator_options[:search_count].blank?
|
||||
return @paginator_options[:exact_count] if @paginator_options[:exact_count]
|
||||
|
||||
when :count
|
||||
if @paginator_options.key?(:search_count) && @paginator_options[:search_count].blank?
|
||||
1_000_000
|
||||
elsif @paginator_options[:count]
|
||||
@paginator_options[:count]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
real_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -8,6 +8,7 @@ module Danbooru
|
||||
@_current_page = options[:current_page]
|
||||
@_records_per_page = options[:per_page]
|
||||
@_total_count = options[:total]
|
||||
@_max_numbered_pages = options[:max_numbered_pages] || Danbooru.config.max_numbered_pages
|
||||
real_array = orig_array || []
|
||||
@_orig_size = real_array.size
|
||||
if options[:mode] == :sequential
|
||||
@ -41,6 +42,10 @@ module Danbooru
|
||||
end
|
||||
end
|
||||
|
||||
def max_numbered_pages
|
||||
@_max_numbered_pages
|
||||
end
|
||||
|
||||
def total_pages
|
||||
if records_per_page > 0
|
||||
(total_count.to_f / records_per_page).ceil
|
||||
@ -55,10 +60,10 @@ module Danbooru
|
||||
|
||||
attr_reader :current_page, :sequential_paginator_mode
|
||||
|
||||
def paginate(page, options = {})
|
||||
def paginate(page, options)
|
||||
paginated, mode = paginate_base(page, options)
|
||||
|
||||
new_opts = {mode: mode, seq_mode: sequential_paginator_mode,
|
||||
new_opts = {mode: mode, seq_mode: sequential_paginator_mode, max_numbered_pages: max_numbered_pages,
|
||||
per_page: records_per_page, total: total_count, current_page: current_page}
|
||||
if options[:results] == :results
|
||||
PaginatedArray.new(paginated.results, new_opts)
|
||||
@ -94,7 +99,7 @@ module Danbooru
|
||||
end
|
||||
|
||||
def paginate_numbered(page)
|
||||
search.definition.update(size: records_per_page, from: (page - 1) * records_per_page, track_total_hits: Danbooru.config.max_numbered_pages * records_per_page + 1)
|
||||
search.definition.update(size: records_per_page, from: (page - 1) * records_per_page, track_total_hits: (max_numbered_pages * records_per_page) + 1)
|
||||
@current_page = page
|
||||
|
||||
self
|
||||
@ -105,13 +110,7 @@ module Danbooru
|
||||
self
|
||||
end
|
||||
|
||||
def total_count
|
||||
return option_for(:count) if option_for(:count)
|
||||
|
||||
response_hits_total
|
||||
end
|
||||
|
||||
def response_hits_total
|
||||
def real_count
|
||||
if response['hits']['total'].respond_to?(:keys)
|
||||
response['hits']['total']['value']
|
||||
else
|
||||
@ -122,13 +121,13 @@ module Danbooru
|
||||
def exists?
|
||||
search.definition[:body]&.delete(:sort)
|
||||
search.definition.update(from: 0, size: 1, terminate_after: 1, sort: '_doc', _source: false, track_total_hits: false)
|
||||
response_hits_total > 0
|
||||
real_count > 0
|
||||
end
|
||||
|
||||
def count_only
|
||||
search.definition[:body]&.delete(:sort)
|
||||
search.definition.update(from: 0, size: 0, sort: '_doc', _source: false, track_total_hits: true)
|
||||
response_hits_total
|
||||
real_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
18
app/logical/dummy_ticket.rb
Normal file
18
app/logical/dummy_ticket.rb
Normal file
@ -0,0 +1,18 @@
|
||||
class DummyTicket
|
||||
def initialize(accused, post_id)
|
||||
@ticket = Ticket.new(
|
||||
id: 0,
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now,
|
||||
creator_id: User.system.id,
|
||||
disp_id: 0,
|
||||
status: "pending",
|
||||
qtype: "user",
|
||||
reason: "User ##{accused.id} (#{accused.name}) tried to reupload destroyed post ##{post_id}",
|
||||
)
|
||||
end
|
||||
|
||||
def notify
|
||||
@ticket.push_pubsub("create")
|
||||
end
|
||||
end
|
@ -45,6 +45,26 @@ module FileMethods
|
||||
end
|
||||
end
|
||||
|
||||
def is_ai_generated?(file_path)
|
||||
return false if !is_image?
|
||||
|
||||
image = Vips::Image.new_from_file(file_path)
|
||||
fetch = lambda do |key|
|
||||
value = image.get(key)
|
||||
value.encode("ASCII", invalid: :replace, undef: :replace).gsub("\u0000", "")
|
||||
rescue Vips::Error
|
||||
""
|
||||
end
|
||||
|
||||
return true if fetch.call("png-comment-0-parameters").present?
|
||||
return true if fetch.call("png-comment-0-Dream").present?
|
||||
return true if fetch.call("exif-ifd0-Software").include?("NovelAI") || fetch.call("png-comment-2-Software").include?("NovelAI")
|
||||
return true if ["exif-ifd0-ImageDescription", "exif-ifd2-UserComment", "png-comment-4-Comment"].any? { |field| fetch.call(field).include?('"sampler": "') }
|
||||
exif_data = fetch.call("exif-data")
|
||||
return true if ["Model hash", "OpenAI", "NovelAI"].any? { |marker| exif_data.include?(marker) }
|
||||
false
|
||||
end
|
||||
|
||||
def file_header_to_file_ext(file_path)
|
||||
File.open file_path do |bin|
|
||||
mime_type = Marcel::MimeType.for(bin)
|
||||
|
@ -6,13 +6,13 @@ class FileValidator
|
||||
@file_path = file_path
|
||||
end
|
||||
|
||||
def validate
|
||||
validate_file_ext
|
||||
validate_file_size
|
||||
def validate(max_file_sizes: Danbooru.config.max_file_sizes, max_width: Danbooru.config.max_image_width, max_height: Danbooru.config.max_image_height)
|
||||
validate_file_ext(max_file_sizes)
|
||||
validate_file_size(max_file_sizes)
|
||||
validate_file_integrity
|
||||
validate_video_container_format
|
||||
validate_video_duration
|
||||
validate_resolution
|
||||
validate_resolution(max_width, max_height)
|
||||
end
|
||||
|
||||
def validate_file_integrity
|
||||
@ -21,35 +21,35 @@ class FileValidator
|
||||
end
|
||||
end
|
||||
|
||||
def validate_file_ext
|
||||
if Danbooru.config.max_file_sizes.keys.exclude? record.file_ext
|
||||
record.errors.add(:file_ext, "#{record.file_ext} is invalid (only JPEG, PNG, GIF, and WebM files are allowed")
|
||||
def validate_file_ext(max_file_sizes)
|
||||
if max_file_sizes.keys.exclude? record.file_ext
|
||||
record.errors.add(:file_ext, "#{record.file_ext} is invalid (only #{max_file_sizes.keys.to_sentence} files are allowed")
|
||||
throw :abort
|
||||
end
|
||||
end
|
||||
|
||||
def validate_file_size
|
||||
def validate_file_size(max_file_sizes)
|
||||
if record.file_size <= 16
|
||||
record.errors.add(:file_size, "is too small")
|
||||
end
|
||||
max_size = Danbooru.config.max_file_sizes.fetch(record.file_ext, 0)
|
||||
max_size = max_file_sizes.fetch(record.file_ext, 0)
|
||||
if record.file_size > max_size
|
||||
record.errors.add(:file_size, "is too large. Maximum allowed for this file type is #{max_size / (1024 * 1024)} MiB")
|
||||
record.errors.add(:file_size, "is too large. Maximum allowed for this file type is #{ApplicationController.helpers.number_to_human_size(max_size)}")
|
||||
end
|
||||
if record.is_animated_png?(file_path) && record.file_size > Danbooru.config.max_apng_file_size
|
||||
record.errors.add(:file_size, "is too large. Maximum allowed for this file type is #{Danbooru.config.max_apng_file_size / (1024*1024)} MiB")
|
||||
record.errors.add(:file_size, "is too large. Maximum allowed for this file type is #{ApplicationController.helpers.number_to_human_size(Danbooru.config.max_apng_file_size)}")
|
||||
end
|
||||
end
|
||||
|
||||
def validate_resolution
|
||||
def validate_resolution(max_width, max_height)
|
||||
resolution = record.image_width.to_i * record.image_height.to_i
|
||||
|
||||
if resolution > Danbooru.config.max_image_resolution
|
||||
record.errors.add(:base, "image resolution is too large (resolution: #{(resolution / 1_000_000.0).round(1)} megapixels (#{record.image_width}x#{record.image_height}); max: #{Danbooru.config.max_image_resolution / 1_000_000} megapixels)")
|
||||
elsif record.image_width > Danbooru.config.max_image_width
|
||||
record.errors.add(:image_width, "is too large (width: #{record.image_width}; max width: #{Danbooru.config.max_image_width})")
|
||||
elsif record.image_height > Danbooru.config.max_image_height
|
||||
record.errors.add(:image_height, "is too large (height: #{record.image_height}; max height: #{Danbooru.config.max_image_height})")
|
||||
elsif record.image_width > max_width
|
||||
record.errors.add(:image_width, "is too large (width: #{record.image_width}; max width: #{max_width})")
|
||||
elsif record.image_height > max_height
|
||||
record.errors.add(:image_height, "is too large (height: #{record.image_height}; max height: #{max_height})")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -13,7 +13,12 @@ module Moderator
|
||||
elsif params[:user_name].present?
|
||||
search_by_user_name(params[:user_name].split(/,/).map(&:strip), with_history)
|
||||
elsif params[:ip_addr].present?
|
||||
search_by_ip_addr(params[:ip_addr].split(/,/).map(&:strip), with_history)
|
||||
ip_addrs = params[:ip_addr].split(/,/).map(&:strip)
|
||||
if params[:add_ip_mask].to_s.truthy? && ip_addrs.count == 1 && ip_addrs[0].exclude?("/")
|
||||
mask = IPAddr.new(ip_addrs[0]).ipv4? ? 24 : 64
|
||||
ip_addrs[0] = "#{ip_addrs[0]}/#{mask}"
|
||||
end
|
||||
search_by_ip_addr(ip_addrs, with_history)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -12,14 +12,6 @@ module PostSets
|
||||
nil
|
||||
end
|
||||
|
||||
def unknown_post_count?
|
||||
false
|
||||
end
|
||||
|
||||
def use_sequential_paginator?
|
||||
false
|
||||
end
|
||||
|
||||
def fill_tag_types(posts)
|
||||
tag_array = []
|
||||
posts.each do |p|
|
||||
|
@ -20,7 +20,7 @@ module PostSets
|
||||
def posts
|
||||
@post_count ||= ::Post.tag_match("fav:#{@user.name} status:any").count_only
|
||||
@posts ||= begin
|
||||
favs = ::Favorite.for_user(@user.id).includes(:post).order(created_at: :desc).paginate(page, count: @post_count, limit: @limit)
|
||||
favs = ::Favorite.for_user(@user.id).includes(:post).order(created_at: :desc).paginate(page, exact_count: @post_count, limit: @limit)
|
||||
new_opts = {mode: :numbered, per_page: favs.records_per_page, total: @post_count, current_page: current_page}
|
||||
::Danbooru::Paginator::PaginatedArray.new(favs.map {|f| f.post},
|
||||
new_opts
|
||||
|
@ -1,9 +1,7 @@
|
||||
require_relative "../danbooru/paginator/elasticsearch_extensions"
|
||||
|
||||
module PostSets
|
||||
class Pool < PostSets::Base
|
||||
module ActiveRecordExtension
|
||||
attr_accessor :total_pages, :current_page
|
||||
end
|
||||
|
||||
attr_reader :pool, :page
|
||||
|
||||
def initialize(pool, page = 1)
|
||||
@ -25,11 +23,9 @@ module PostSets
|
||||
|
||||
def posts
|
||||
@posts ||= begin
|
||||
x = pool.posts(:offset => offset, :limit => limit)
|
||||
x.extend(ActiveRecordExtension)
|
||||
x.total_pages = total_pages
|
||||
x.current_page = current_page
|
||||
x
|
||||
posts = pool.posts(offset: offset, limit: limit)
|
||||
options = { mode: :numbered, per_page: limit, total: pool.post_count, current_page: current_page }
|
||||
Danbooru::Paginator::PaginatedArray.new(posts, options)
|
||||
end
|
||||
end
|
||||
|
||||
@ -45,14 +41,6 @@ module PostSets
|
||||
@presenter ||= PostSetPresenters::Pool.new(self)
|
||||
end
|
||||
|
||||
def total_pages
|
||||
(pool.post_count.to_f / limit).ceil
|
||||
end
|
||||
|
||||
def size
|
||||
posts.size
|
||||
end
|
||||
|
||||
def current_page
|
||||
[page.to_i, 1].max
|
||||
end
|
||||
|
@ -1,16 +1,9 @@
|
||||
module PostSets
|
||||
class PoolGallery < PostSets::Base
|
||||
attr_reader :page, :per_page, :pools
|
||||
attr_reader :pools
|
||||
|
||||
def initialize(pools, page = 1, per_page = nil)
|
||||
def initialize(pools)
|
||||
@pools = pools
|
||||
@page = page
|
||||
@per_page = (per_page || CurrentUser.per_page).to_i
|
||||
@per_page = 200 if @per_page > 200
|
||||
end
|
||||
|
||||
def current_page
|
||||
[page.to_i, 1].max
|
||||
end
|
||||
|
||||
def presenter
|
||||
|
@ -66,10 +66,6 @@ module PostSets
|
||||
random || (Tag.has_metatag?(tag_array, :order) == "random" && !Tag.has_metatag?(tag_array, :randseed))
|
||||
end
|
||||
|
||||
def use_sequential_paginator?
|
||||
unknown_post_count? && !CurrentUser.is_privileged?
|
||||
end
|
||||
|
||||
def posts
|
||||
@posts ||= begin
|
||||
temp = ::Post.tag_match(tag_string).paginate(page, limit: per_page, includes: [:uploader])
|
||||
@ -86,10 +82,6 @@ module PostSets
|
||||
_posts
|
||||
end
|
||||
|
||||
def unknown_post_count?
|
||||
post_count == Danbooru.config.blank_tag_search_fast_count
|
||||
end
|
||||
|
||||
def hide_from_crawler?
|
||||
!is_empty_tag?
|
||||
end
|
||||
|
@ -96,11 +96,9 @@ private
|
||||
end
|
||||
|
||||
def authenticate_api_key(name, api_key)
|
||||
CurrentUser.user = User.authenticate_api_key(name, api_key)
|
||||
|
||||
if CurrentUser.user.nil?
|
||||
raise AuthenticationFailure.new
|
||||
end
|
||||
user = User.authenticate_api_key(name, api_key)
|
||||
raise AuthenticationFailure if user.nil?
|
||||
CurrentUser.user = user
|
||||
end
|
||||
|
||||
def load_session_user
|
||||
|
@ -3,6 +3,7 @@ class StorageManager
|
||||
|
||||
DEFAULT_BASE_DIR = "#{Rails.root}/public/data"
|
||||
IMAGE_TYPES = %i[preview large crop original]
|
||||
MASCOT_PREFIX = "mascots"
|
||||
|
||||
attr_reader :base_url, :base_dir, :hierarchical, :large_image_prefix, :protected_prefix, :base_path, :replacement_prefix
|
||||
|
||||
@ -185,6 +186,24 @@ class StorageManager
|
||||
"#{base_dir}/#{replacement_prefix}/#{subdir}#{file}"
|
||||
end
|
||||
|
||||
def store_mascot(io, mascot)
|
||||
store(io, mascot_path(mascot.md5, mascot.file_ext))
|
||||
end
|
||||
|
||||
def mascot_path(md5, file_ext)
|
||||
file = "#{md5}.#{file_ext}"
|
||||
"#{base_dir}/#{MASCOT_PREFIX}/#{file}"
|
||||
end
|
||||
|
||||
def mascot_url(mascot)
|
||||
file = "#{mascot.md5}.#{mascot.file_ext}"
|
||||
"#{base_url}#{base_path}/#{MASCOT_PREFIX}/#{file}"
|
||||
end
|
||||
|
||||
def delete_mascot(md5, file_ext)
|
||||
delete(mascot_path(md5, file_ext))
|
||||
end
|
||||
|
||||
def subdir_for(md5)
|
||||
hierarchical ? "#{md5[0..1]}/#{md5[2..3]}/" : ""
|
||||
end
|
||||
|
@ -71,6 +71,7 @@ class UploadService
|
||||
tags = []
|
||||
tags += ["animated_gif", "animated"] if upload.is_animated_gif?(file.path)
|
||||
tags += ["animated_png", "animated"] if upload.is_animated_png?(file.path)
|
||||
tags += ["ai_generated"] if upload.is_ai_generated?(file.path)
|
||||
tags.join(" ")
|
||||
end
|
||||
|
||||
|
@ -30,16 +30,19 @@ class UserDeletion
|
||||
end
|
||||
|
||||
def clear_user_settings
|
||||
user.update_columns(recent_tags: '',
|
||||
favorite_tags: '',
|
||||
blacklisted_tags: '',
|
||||
time_zone: "Eastern Time (US & Canada)",
|
||||
email: '',
|
||||
email_verification_key: '1',
|
||||
avatar_id: nil,
|
||||
profile_about: '',
|
||||
profile_artinfo: '',
|
||||
custom_style: '')
|
||||
user.update_columns(
|
||||
recent_tags: '',
|
||||
favorite_tags: '',
|
||||
blacklisted_tags: '',
|
||||
time_zone: "Eastern Time (US & Canada)",
|
||||
email: '',
|
||||
email_verification_key: '1',
|
||||
avatar_id: nil,
|
||||
profile_about: '',
|
||||
profile_artinfo: '',
|
||||
custom_style: '',
|
||||
level: User::Levels::MEMBER,
|
||||
)
|
||||
end
|
||||
|
||||
def reset_password
|
||||
|
@ -20,6 +20,7 @@ class UserEmailChange
|
||||
if User.authenticate(user.name, password).nil?
|
||||
user.errors.add(:base, "Password was incorrect")
|
||||
else
|
||||
user.validate_email_format = true
|
||||
user.email = new_email
|
||||
user.email_verification_key = '1' if Danbooru.config.enable_email_verification?
|
||||
user.save
|
||||
|
@ -1,6 +1,6 @@
|
||||
class UserNameValidator < ActiveModel::EachValidator
|
||||
def validate_each(rec, attr, value)
|
||||
name = value
|
||||
name = value
|
||||
|
||||
rec.errors.add(attr, "already exists") if User.find_by_name(name).present?
|
||||
rec.errors.add(attr, "must be 2 to 20 characters long") if !name.length.between?(2, 20)
|
||||
@ -8,6 +8,7 @@ class UserNameValidator < ActiveModel::EachValidator
|
||||
rec.errors.add(attr, "must not begin with a special character") if name =~ /\A[_\-~']/
|
||||
rec.errors.add(attr, "must not contain consecutive special characters") if name =~ /_{2}|-{2}|~{2}|'{2}/
|
||||
rec.errors.add(attr, "cannot begin or end with an underscore") if name =~ /\A_|_\z/
|
||||
rec.errors.add(attr, "cannot consist of numbers only") if name =~ /\A[0-9]+\z/
|
||||
rec.errors.add(attr, "cannot be the string 'me'") if name.downcase == 'me'
|
||||
end
|
||||
end
|
||||
|
@ -4,8 +4,8 @@ class VoteManager
|
||||
retries = 5
|
||||
score = score.to_i
|
||||
begin
|
||||
raise PostVote::Error.new("Invalid vote") unless [1, -1].include?(score)
|
||||
raise PostVote::Error.new("You do not have permission to vote") unless user.is_voter?
|
||||
raise UserVote::Error.new("Invalid vote") unless [1, -1].include?(score)
|
||||
raise UserVote::Error.new("You do not have permission to vote") unless user.is_voter?
|
||||
target_isolation = !Rails.env.test? ? { isolation: :serializable } : {}
|
||||
PostVote.transaction(**target_isolation) do
|
||||
PostVote.uncached do
|
||||
@ -13,7 +13,7 @@ class VoteManager
|
||||
score_modifier = score
|
||||
old_vote = PostVote.where(user_id: user.id, post_id: post.id).first
|
||||
if old_vote
|
||||
raise PostVote::Error.new("Vote is locked") if old_vote.score == 0
|
||||
raise UserVote::Error.new("Vote is locked") if old_vote.score == 0
|
||||
if old_vote.score == score
|
||||
return :need_unvote
|
||||
else
|
||||
@ -38,9 +38,9 @@ class VoteManager
|
||||
rescue ActiveRecord::SerializationFailure
|
||||
retries -= 1
|
||||
retry if retries > 0
|
||||
raise PostVote::Error.new("Failed to vote, please try again later")
|
||||
raise UserVote::Error.new("Failed to vote, please try again later")
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
raise PostVote::Error.new("You have already voted for this post")
|
||||
raise UserVote::Error.new("You have already voted for this post")
|
||||
end
|
||||
post.update_index
|
||||
@vote
|
||||
@ -55,7 +55,7 @@ class VoteManager
|
||||
post.with_lock do
|
||||
vote = PostVote.where(user_id: user.id, post_id: post.id).first
|
||||
return unless vote
|
||||
raise PostVote::Error.new "You can't remove locked votes" if vote.score == 0 && !force
|
||||
raise UserVote::Error.new "You can't remove locked votes" if vote.score == 0 && !force
|
||||
post.votes.where(user: user).delete_all
|
||||
subtract_vote(post, vote)
|
||||
post.reload
|
||||
@ -65,7 +65,7 @@ class VoteManager
|
||||
rescue ActiveRecord::SerializationFailure
|
||||
retries -= 1
|
||||
retry if retries > 0
|
||||
raise PostVote::Error.new("Failed to unvote, please try again later")
|
||||
raise UserVote::Error.new("Failed to unvote, please try again later")
|
||||
end
|
||||
post.update_index
|
||||
end
|
||||
@ -93,8 +93,8 @@ class VoteManager
|
||||
@vote = nil
|
||||
score = score.to_i
|
||||
begin
|
||||
raise CommentVote::Error.new("Invalid vote") unless [1, -1].include?(score)
|
||||
raise CommentVote::Error.new("You do not have permission to vote") unless user.is_voter?
|
||||
raise UserVote::Error.new("Invalid vote") unless [1, -1].include?(score)
|
||||
raise UserVote::Error.new("You do not have permission to vote") unless user.is_voter?
|
||||
target_isolation = !Rails.env.test? ? { isolation: :serializable } : {}
|
||||
CommentVote.transaction(**target_isolation) do
|
||||
CommentVote.uncached do
|
||||
@ -102,7 +102,7 @@ class VoteManager
|
||||
score_modifier = score
|
||||
old_vote = CommentVote.where(user_id: user.id, comment_id: comment.id).first
|
||||
if old_vote
|
||||
raise CommentVote::Error.new("Vote is locked") if old_vote.score == 0
|
||||
raise UserVote::Error.new("Vote is locked") if old_vote.score == 0
|
||||
if old_vote.score == score
|
||||
return :need_unvote
|
||||
else
|
||||
@ -118,9 +118,9 @@ class VoteManager
|
||||
rescue ActiveRecord::SerializationFailure
|
||||
retries -= 1
|
||||
retry if retries > 0
|
||||
raise PostVote::Error.new("Failed to vote, please try again later.")
|
||||
raise UserVote::Error.new("Failed to vote, please try again later.")
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
raise CommentVote::Error.new("You have already voted for this comment")
|
||||
raise UserVote::Error.new("You have already voted for this comment")
|
||||
end
|
||||
@vote
|
||||
end
|
||||
@ -132,7 +132,7 @@ class VoteManager
|
||||
comment.with_lock do
|
||||
vote = CommentVote.where(user_id: user.id, comment_id: comment.id).first
|
||||
return unless vote
|
||||
raise CommentVote::Error.new("You can't remove locked votes") if vote.score == 0 && !force
|
||||
raise UserVote::Error.new("You can't remove locked votes") if vote.score == 0 && !force
|
||||
CommentVote.where(user_id: user.id, comment_id: comment.id).delete_all
|
||||
Comment.where(id: comment.id).update_all("score = score - #{vote.score}")
|
||||
end
|
||||
|
@ -25,12 +25,6 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
where.not("lower(#{qualified_column_for(attr)}) LIKE ? ESCAPE E'\\\\'", value.downcase.to_escaped_for_sql_like)
|
||||
end
|
||||
|
||||
# https://www.postgresql.org/docs/current/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
|
||||
# "(?e)" means force use of ERE syntax; see sections 9.7.3.1 and 9.7.3.4.
|
||||
def where_regex(attr, value)
|
||||
where("#{qualified_column_for(attr)} ~ ?", "(?e)" + value)
|
||||
end
|
||||
|
||||
def attribute_exact_matches(attribute, value, **options)
|
||||
return all unless value.present?
|
||||
|
||||
@ -312,7 +306,7 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
|
||||
define_method "#{name}=" do |value|
|
||||
if value.respond_to?(:to_str)
|
||||
super value.to_str.scan(parse).map(&cast)
|
||||
super value.to_str.scan(parse).flatten.map(&cast)
|
||||
elsif value.respond_to?(:to_a)
|
||||
super value.to_a
|
||||
else
|
||||
|
@ -182,7 +182,7 @@ class Artist < ApplicationRecord
|
||||
while artists.empty? && url.length > 10
|
||||
u = url.sub(/\/+$/, "") + "/"
|
||||
u = u.to_escaped_for_sql_like.gsub(/\*/, '%') + '%'
|
||||
artists += Artist.joins(:urls).where(["artists.is_active = TRUE AND artist_urls.normalized_url LIKE ? ESCAPE E'\\\\'", u]).limit(10).order("artists.name").all
|
||||
artists += Artist.joins(:urls).where(["artists.is_active = TRUE AND artist_urls.normalized_url ILIKE ? ESCAPE E'\\\\'", u]).limit(10).order("artists.name").all
|
||||
url = File.dirname(url) + "/"
|
||||
|
||||
break if url =~ SITE_BLACKLIST_REGEXP
|
||||
@ -423,24 +423,16 @@ class Artist < ApplicationRecord
|
||||
end
|
||||
|
||||
def any_name_matches(query)
|
||||
if query =~ %r!\A/(.*)/\z!
|
||||
where_regex(:name, $1).or(any_other_name_matches($1)).or(where_regex(:group_name, $1))
|
||||
else
|
||||
normalized_name = normalize_name(query)
|
||||
normalized_name = "*#{normalized_name}*" unless normalized_name.include?("*")
|
||||
where_like(:name, normalized_name).or(any_other_name_like(normalized_name)).or(where_like(:group_name, normalized_name))
|
||||
end
|
||||
normalized_name = normalize_name(query)
|
||||
normalized_name = "*#{normalized_name}*" unless normalized_name.include?("*")
|
||||
where_like(:name, normalized_name).or(any_other_name_like(normalized_name)).or(where_like(:group_name, normalized_name))
|
||||
end
|
||||
|
||||
def url_matches(query)
|
||||
if query =~ %r!\A/(.*)/\z!
|
||||
where(id: ArtistUrl.where_regex(:url, $1).select(:artist_id))
|
||||
elsif query.include?("*")
|
||||
where(id: ArtistUrl.where_like(:url, query).select(:artist_id))
|
||||
elsif query =~ %r!\Ahttps?://!i
|
||||
if query =~ %r!\Ahttps?://!i
|
||||
find_artists(query)
|
||||
else
|
||||
where(id: ArtistUrl.where_like(:url, "*#{query}*").select(:artist_id))
|
||||
where(id: ArtistUrl.search(url_matches: query).select(:artist_id))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,12 +65,9 @@ class ArtistUrl < ApplicationRecord
|
||||
def self.url_attribute_matches(attr, url)
|
||||
if url.blank?
|
||||
all
|
||||
elsif url =~ %r!\A/(.*)/\z!
|
||||
where_regex(attr, $1)
|
||||
elsif url.include?("*")
|
||||
where_ilike(attr, url)
|
||||
else
|
||||
where(attr => normalize(url))
|
||||
url = "*#{url}*" if url.exclude?("*")
|
||||
where_ilike(attr, url)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,24 +1,16 @@
|
||||
class CommentVote < ApplicationRecord
|
||||
class Error < Exception;
|
||||
end
|
||||
|
||||
belongs_to :comment
|
||||
belongs_to :user
|
||||
before_validation :initialize_user, :on => :create
|
||||
validates :user_id, :comment_id, :score, presence: true
|
||||
# validates :user_id, uniqueness: { :scope => :comment_id, :message => "have already voted for this comment" }
|
||||
class CommentVote < UserVote
|
||||
validate :validate_user_can_vote
|
||||
validate :validate_comment_can_be_voted
|
||||
validates :score, inclusion: { :in => [-1, 0, 1], :message => "must be 1 or -1" }
|
||||
|
||||
scope :for_user, ->(uid) {where("user_id = ?", uid)}
|
||||
|
||||
|
||||
def self.for_comments_and_user(comment_ids, user_id)
|
||||
return {} unless user_id
|
||||
CommentVote.where(comment_id: comment_ids, user_id: user_id).index_by(&:comment_id)
|
||||
end
|
||||
|
||||
def self.model_creator_column
|
||||
:creator
|
||||
end
|
||||
|
||||
def validate_user_can_vote
|
||||
allowed = user.can_comment_vote_with_reason
|
||||
if allowed != true
|
||||
@ -36,83 +28,4 @@ class CommentVote < ApplicationRecord
|
||||
errors.add :base, "You cannot vote on sticky comments"
|
||||
end
|
||||
end
|
||||
|
||||
def is_positive?
|
||||
score == 1
|
||||
end
|
||||
|
||||
def is_negative?
|
||||
score == -1
|
||||
end
|
||||
|
||||
def is_locked?
|
||||
score == 0
|
||||
end
|
||||
|
||||
def initialize_user
|
||||
self.user_id ||= CurrentUser.user.id
|
||||
self.user_ip_addr ||= CurrentUser.ip_addr
|
||||
end
|
||||
|
||||
module SearchMethods
|
||||
def search(params)
|
||||
q = super
|
||||
|
||||
if params[:comment_id].present?
|
||||
q = q.where("comment_id = ?", params[:comment_id].to_i)
|
||||
end
|
||||
|
||||
if params[:user_name].present?
|
||||
user_id = User.name_to_id(params[:user_name])
|
||||
if user_id
|
||||
q = q.where('user_id = ?', user_id)
|
||||
else
|
||||
q = q.none
|
||||
end
|
||||
end
|
||||
|
||||
if params[:user_id].present?
|
||||
q = q.where('user_id = ?', params[:user_id].to_i)
|
||||
end
|
||||
|
||||
allow_complex_parameters = (params.keys & %w[comment_id user_name user_id]).any?
|
||||
|
||||
if allow_complex_parameters
|
||||
if params[:timeframe].present?
|
||||
q = q.where("comment_votes.updated_at >= ?", params[:timeframe].to_i.days.ago)
|
||||
end
|
||||
|
||||
if params[:user_ip_addr].present?
|
||||
q = q.where("user_ip_addr <<= ?", params[:user_ip_addr])
|
||||
end
|
||||
|
||||
if params[:score].present?
|
||||
q = q.where("comment_votes.score = ?", params[:score])
|
||||
end
|
||||
|
||||
if params[:comment_creator_name].present?
|
||||
comment_creator_id = User.name_to_id(params[:comment_creator_name])
|
||||
if comment_creator_id
|
||||
q = q.joins(:comment).where("comments.creator_id = ?", comment_creator_id)
|
||||
else
|
||||
q = q.none
|
||||
end
|
||||
end
|
||||
|
||||
if params[:duplicates_only] == "1"
|
||||
subselect = CommentVote.search(params.except("duplicates_only")).select(:user_ip_addr).group(:user_ip_addr).having("count(user_ip_addr) > 1").reorder("")
|
||||
q = q.where(user_ip_addr: subselect)
|
||||
end
|
||||
end
|
||||
|
||||
if params[:order] == "ip_addr" && allow_complex_parameters
|
||||
q = q.order(:user_ip_addr)
|
||||
else
|
||||
q = q.apply_default_order(params)
|
||||
end
|
||||
q
|
||||
end
|
||||
end
|
||||
|
||||
extend SearchMethods
|
||||
end
|
||||
|
89
app/models/mascot.rb
Normal file
89
app/models/mascot.rb
Normal file
@ -0,0 +1,89 @@
|
||||
class Mascot < ApplicationRecord
|
||||
belongs_to_creator
|
||||
|
||||
attr_accessor :mascot_file
|
||||
|
||||
validates :display_name, :background_color, :artist_url, :artist_name, presence: true
|
||||
validates :artist_url, format: { with: %r{\Ahttps?://}, message: "must start with http:// or https://" }
|
||||
validates :mascot_file, presence: true, on: :create
|
||||
validate :set_file_properties
|
||||
validates :md5, uniqueness: true
|
||||
validate if: :mascot_file do |mascot|
|
||||
max_file_sizes = { "jpg" => 500.kilobytes, "png" => 500.kilobytes }
|
||||
FileValidator.new(mascot, mascot_file.path).validate(max_file_sizes: max_file_sizes, max_width: 1_000, max_height: 1_000)
|
||||
end
|
||||
|
||||
after_commit :invalidate_cache
|
||||
after_save_commit :write_storage_file
|
||||
after_destroy_commit :remove_storage_file
|
||||
|
||||
def set_file_properties
|
||||
return if mascot_file.blank?
|
||||
|
||||
self.file_ext = file_header_to_file_ext(mascot_file.path)
|
||||
self.md5 = Digest::MD5.file(mascot_file.path).hexdigest
|
||||
end
|
||||
|
||||
def write_storage_file
|
||||
return if mascot_file.blank?
|
||||
|
||||
Danbooru.config.storage_manager.delete_mascot(md5_previously_was, file_ext_previously_was)
|
||||
Danbooru.config.storage_manager.store_mascot(mascot_file, self)
|
||||
end
|
||||
|
||||
def self.active_for_browser
|
||||
Cache.get("active_mascots", 1.day) do
|
||||
query = Mascot.where(active: true)
|
||||
query = query.where(safe_mode_only: false) if !Danbooru.config.safe_mode?
|
||||
mascots = query.map do |mascot|
|
||||
mascot.slice(:id, :background_color, :artist_url, :artist_name).merge(background_url: mascot.url_path)
|
||||
end
|
||||
mascots.index_by { |mascot| mascot["id"] }
|
||||
end
|
||||
end
|
||||
|
||||
def invalidate_cache
|
||||
Cache.delete("active_mascots")
|
||||
end
|
||||
|
||||
def remove_storage_file
|
||||
Danbooru.config.storage_manager.delete_mascot(md5, file_ext)
|
||||
end
|
||||
|
||||
def url_path
|
||||
Danbooru.config.storage_manager.mascot_url(self)
|
||||
end
|
||||
|
||||
def file_path
|
||||
Danbooru.config.storage_manager.mascot_path(self)
|
||||
end
|
||||
|
||||
concerning :ValidationMethods do
|
||||
def dimensions
|
||||
@dimensions ||= calculate_dimensions(mascot_file.path)
|
||||
end
|
||||
|
||||
def image_width
|
||||
dimensions[0]
|
||||
end
|
||||
|
||||
def image_height
|
||||
dimensions[1]
|
||||
end
|
||||
|
||||
def file_size
|
||||
@file_size ||= Danbooru.config.storage_manager.open(mascot_file.path).size
|
||||
end
|
||||
end
|
||||
|
||||
def self.search(params)
|
||||
q = super
|
||||
q.order("lower(artist_name)")
|
||||
end
|
||||
|
||||
def method_attributes
|
||||
super + [:url_path]
|
||||
end
|
||||
|
||||
include FileMethods
|
||||
end
|
@ -39,6 +39,9 @@ class ModAction < ApplicationRecord
|
||||
:help_update,
|
||||
:ip_ban_create,
|
||||
:ip_ban_delete,
|
||||
:mascot_create,
|
||||
:mascot_update,
|
||||
:mascot_delete,
|
||||
:pool_delete,
|
||||
:report_reason_create,
|
||||
:report_reason_delete,
|
||||
@ -76,7 +79,8 @@ class ModAction < ApplicationRecord
|
||||
:mass_update,
|
||||
:nuke_tag,
|
||||
|
||||
:takedown_process
|
||||
:takedown_delete,
|
||||
:takedown_process,
|
||||
]
|
||||
|
||||
def self.search(params)
|
||||
|
@ -6,12 +6,12 @@ class NewsUpdate < ApplicationRecord
|
||||
after_destroy :invalidate_cache
|
||||
|
||||
def self.recent
|
||||
@recent_news ||= Cache.get('recent_news', 1.day) do
|
||||
self.order('id desc').first(1)
|
||||
Cache.get("recent_news_v2", 1.day) do
|
||||
order("id desc").first
|
||||
end
|
||||
end
|
||||
|
||||
def invalidate_cache
|
||||
Cache.delete('recent_news')
|
||||
Cache.delete("recent_news_v2")
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ class Pool < ApplicationRecord
|
||||
class RevertError < Exception;
|
||||
end
|
||||
|
||||
array_attribute :post_ids, parse: /\d+/, cast: :to_i
|
||||
array_attribute :post_ids, parse: %r{(?:https://(?:e621|e926)\.net/posts/)?(\d+)}i, cast: :to_i
|
||||
belongs_to_creator
|
||||
|
||||
validates :name, uniqueness: { case_sensitive: false, if: :name_changed? }
|
||||
|
@ -42,6 +42,13 @@ class PostReplacement < ApplicationRecord
|
||||
|
||||
def no_pending_duplicates
|
||||
return true if is_backup
|
||||
|
||||
if (destroyed_post = DestroyedPost.find_by(md5: md5))
|
||||
errors.add(:base, "An unexpected errror occured")
|
||||
DummyTicket.new(creator, destroyed_post.post_id).notify
|
||||
return
|
||||
end
|
||||
|
||||
post = Post.where(md5: md5).first
|
||||
if post
|
||||
self.errors.add(:md5, "duplicate of existing post ##{post.id}")
|
||||
|
@ -1,6 +1,5 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
class PostSet < ApplicationRecord
|
||||
array_attribute :post_ids, parse: /\d+/, cast: :to_i
|
||||
array_attribute :post_ids, parse: %r{(?:https://(?:e621|e926)\.net/posts/)?(\d+)}i, cast: :to_i
|
||||
|
||||
has_many :post_set_maintainers, dependent: :destroy do
|
||||
def in_cooldown(user)
|
||||
|
@ -1,19 +1,8 @@
|
||||
class PostVote < ApplicationRecord
|
||||
class Error < Exception ; end
|
||||
|
||||
belongs_to :post
|
||||
belongs_to :user
|
||||
|
||||
after_initialize :initialize_attributes, if: :new_record?
|
||||
class PostVote < UserVote
|
||||
validate :validate_user_can_vote
|
||||
validates :post_id, :user_id, :score, presence: true
|
||||
validates :score, inclusion: { :in => [1, 0, -1] }
|
||||
|
||||
scope :for_user, ->(uid) {where("user_id = ?", uid)}
|
||||
|
||||
def initialize_attributes
|
||||
self.user_id ||= CurrentUser.user.id
|
||||
self.user_ip_addr ||= CurrentUser.ip_addr
|
||||
def self.model_creator_column
|
||||
:uploader
|
||||
end
|
||||
|
||||
def validate_user_can_vote
|
||||
@ -28,57 +17,4 @@ class PostVote < ApplicationRecord
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
module SearchMethods
|
||||
def search(params)
|
||||
q = super
|
||||
|
||||
if params[:post_id].present?
|
||||
q = q.where('post_id = ?', params[:post_id])
|
||||
end
|
||||
|
||||
if params[:user_name].present?
|
||||
user_id = User.name_to_id(params[:user_name])
|
||||
if user_id
|
||||
q = q.where('user_id = ?', user_id)
|
||||
else
|
||||
q = q.none
|
||||
end
|
||||
end
|
||||
|
||||
if params[:user_id].present?
|
||||
q = q.where('user_id = ?', params[:user_id].to_i)
|
||||
end
|
||||
|
||||
allow_complex_parameters = (params.keys & %w[post_id user_name user_id]).any?
|
||||
|
||||
if allow_complex_parameters
|
||||
if params[:timeframe].present?
|
||||
q = q.where("updated_at >= ?", params[:timeframe].to_i.days.ago)
|
||||
end
|
||||
|
||||
if params[:user_ip_addr].present?
|
||||
q = q.where("user_ip_addr <<= ?", params[:user_ip_addr])
|
||||
end
|
||||
|
||||
if params[:score].present?
|
||||
q = q.where("score = ?", params[:score])
|
||||
end
|
||||
|
||||
if params[:duplicates_only] == "1"
|
||||
subselect = PostVote.search(params.except("duplicates_only")).select(:user_ip_addr).group(:user_ip_addr).having("count(user_ip_addr) > 1").reorder("")
|
||||
q = q.where(user_ip_addr: subselect)
|
||||
end
|
||||
end
|
||||
|
||||
if params[:order] == "ip_addr" && allow_complex_parameters
|
||||
q = q.order(:user_ip_addr)
|
||||
else
|
||||
q = q.apply_default_order(params)
|
||||
end
|
||||
q
|
||||
end
|
||||
end
|
||||
|
||||
extend SearchMethods
|
||||
end
|
||||
|
@ -88,8 +88,7 @@ class Takedown < ApplicationRecord
|
||||
def add_posts_by_ids!(ids)
|
||||
added_ids = []
|
||||
with_lock do
|
||||
ids = ids.gsub(/(https?:\/\/)?(e621|e926)\.net\/posts\/(\d+)/i, '\3')
|
||||
self.post_ids = (post_array + ids.scan(/\d+/).map(&:to_i)).uniq.join(' ')
|
||||
self.post_ids = (post_array + matching_post_ids(ids)).uniq.join(' ')
|
||||
added_ids = self.post_array - self.post_array_was
|
||||
save!
|
||||
end
|
||||
@ -107,10 +106,14 @@ class Takedown < ApplicationRecord
|
||||
|
||||
def remove_posts_by_ids!(ids)
|
||||
with_lock do
|
||||
self.post_ids = (post_array - ids.scan(/\d+/).map(&:to_i)).uniq.join(' ')
|
||||
self.post_ids = (post_array - matching_post_ids(ids)).uniq.join(' ')
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
||||
def matching_post_ids(input)
|
||||
input.scan(%r{(?:https://(?:e621|e926)\.net/posts/)?(\d+)}i).flatten.map(&:to_i).uniq
|
||||
end
|
||||
end
|
||||
|
||||
module PostMethods
|
||||
@ -119,12 +122,12 @@ class Takedown < ApplicationRecord
|
||||
end
|
||||
|
||||
def normalize_post_ids
|
||||
self.post_ids = post_ids.scan(/\d+/).uniq.join(' ')
|
||||
self.post_ids = matching_post_ids(post_ids).join(' ')
|
||||
end
|
||||
|
||||
def normalize_deleted_post_ids
|
||||
posts = post_ids.scan(/\d+/).uniq
|
||||
del_posts = del_post_ids.scan(/\d+/).uniq
|
||||
posts = matching_post_ids(post_ids)
|
||||
del_posts = matching_post_ids(del_post_ids)
|
||||
del_posts = del_posts & posts # ensure that all deleted posts are also posts
|
||||
self.del_post_ids = del_posts.join(' ')
|
||||
end
|
||||
@ -135,7 +138,7 @@ class Takedown < ApplicationRecord
|
||||
end
|
||||
|
||||
def del_post_array
|
||||
del_post_ids.scan(/\d+/).map(&:to_i)
|
||||
matching_post_ids(del_post_ids)
|
||||
end
|
||||
|
||||
def actual_deleted_posts
|
||||
@ -143,11 +146,11 @@ class Takedown < ApplicationRecord
|
||||
end
|
||||
|
||||
def post_array
|
||||
post_ids.scan(/\d+/).map(&:to_i)
|
||||
matching_post_ids(post_ids)
|
||||
end
|
||||
|
||||
def post_array_was
|
||||
post_ids_was.scan(/\d+/).map(&:to_i)
|
||||
matching_post_ids(post_ids_was)
|
||||
end
|
||||
|
||||
def actual_posts
|
||||
|
@ -62,7 +62,7 @@ class Ticket < ApplicationRecord
|
||||
|
||||
def can_see_details?(user)
|
||||
if content
|
||||
content.visible?(user)
|
||||
content.visible?(user) || (user.id == creator_id)
|
||||
else
|
||||
true
|
||||
end
|
||||
|
@ -179,6 +179,12 @@ class Upload < ApplicationRecord
|
||||
return
|
||||
end
|
||||
|
||||
if (destroyed_post = DestroyedPost.find_by(md5: md5))
|
||||
errors.add(:base, "An unexpected errror occured")
|
||||
DummyTicket.new(uploader, destroyed_post.post_id).notify
|
||||
return
|
||||
end
|
||||
|
||||
replacements = PostReplacement.pending.where(md5: md5)
|
||||
replacements = replacements.where.not(id: replacement_id) if replacement_id
|
||||
|
||||
|
@ -67,25 +67,16 @@ class User < ApplicationRecord
|
||||
include Danbooru::HasBitFlags
|
||||
has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs"
|
||||
|
||||
attr_accessor :password, :old_password
|
||||
attr_accessor :password, :old_password, :validate_email_format, :skip_email_blank_check
|
||||
|
||||
after_initialize :initialize_attributes, if: :new_record?
|
||||
|
||||
before_validation :normalize_email
|
||||
if Danbooru.config.enable_email_verification?
|
||||
validates :email, presence: { on: :create }
|
||||
validates :email, presence: { on: :update, if: ->(rec) { rec.email_changed? } }
|
||||
validates :email, uniqueness: { case_sensitive: false, on: :update, if: ->(rec) { rec.email.present? && rec.saved_change_to_email? } }
|
||||
validates :email, uniqueness: { case_sensitive: false, on: :update, if: ->(rec) { rec.email.present? && rec.email_changed? } }
|
||||
validates :email, uniqueness: { case_sensitive: false, on: :create }
|
||||
validates :email, format: { with: /\A.+@[^ ,;@]+\.[^ ,;@]+\z/, on: :create }
|
||||
validates :email, format: { with: /\A.+@[^ ,;@]+\.[^ ,;@]+\z/, on: :update, if: ->(rec) { rec.email_changed? } }
|
||||
else
|
||||
validates :email, uniqueness: { case_sensitive: false, on: :create, if: ->(rec) { rec.email.present?} }
|
||||
end
|
||||
validates :email, presence: { if: :enable_email_verification?, unless: :skip_email_blank_check }
|
||||
validates :email, uniqueness: { case_sensitive: false, if: :enable_email_verification? }
|
||||
validates :email, uniqueness: { case_sensitive: false, on: :create, if: ->(rec) { rec.email.present? && !Danbooru.config.enable_email_verification? } }
|
||||
validates :email, format: { with: /\A.+@[^ ,;@]+\.[^ ,;@]+\z/, if: :enable_email_verification?, unless: ->(rec) { rec.email.blank? && !rec.email_changed? && rec.skip_email_blank_check } }
|
||||
validate :validate_email_address_allowed, on: [:create, :update], if: ->(rec) { (rec.new_record? && rec.email.present?) || (rec.email.present? && rec.email_changed?) }
|
||||
|
||||
|
||||
validates :name, user_name: true, on: :create
|
||||
validates :default_image_size, inclusion: { :in => %w(large fit fitv original) }
|
||||
validates :per_page, inclusion: { :in => 1..320 }
|
||||
@ -402,8 +393,8 @@ class User < ApplicationRecord
|
||||
update_attribute(:email_verification_key, nil)
|
||||
end
|
||||
|
||||
def normalize_email
|
||||
self.email = nil if email.blank?
|
||||
def enable_email_verification?
|
||||
Danbooru.config.enable_email_verification? && validate_email_format
|
||||
end
|
||||
|
||||
def validate_email_address_allowed
|
||||
|
101
app/models/user_vote.rb
Normal file
101
app/models/user_vote.rb
Normal file
@ -0,0 +1,101 @@
|
||||
class UserVote < ApplicationRecord
|
||||
class Error < Exception; end
|
||||
|
||||
self.abstract_class = true
|
||||
|
||||
belongs_to :user
|
||||
validates :score, inclusion: { in: [-1, 0, 1], message: "must be 1 or -1" }
|
||||
after_initialize :initialize_attributes, if: :new_record?
|
||||
scope :for_user, ->(uid) { where("user_id = ?", uid) }
|
||||
|
||||
def self.inherited(child_class)
|
||||
super
|
||||
child_class.class_eval do
|
||||
belongs_to model_type
|
||||
end
|
||||
end
|
||||
|
||||
# PostVote => :post
|
||||
def self.model_type
|
||||
model_name.singular.delete_suffix("_vote").to_sym
|
||||
end
|
||||
|
||||
def initialize_attributes
|
||||
self.user_id ||= CurrentUser.user.id
|
||||
self.user_ip_addr ||= CurrentUser.ip_addr
|
||||
end
|
||||
|
||||
def is_positive?
|
||||
score == 1
|
||||
end
|
||||
|
||||
def is_negative?
|
||||
score == -1
|
||||
end
|
||||
|
||||
def is_locked?
|
||||
score == 0
|
||||
end
|
||||
|
||||
module SearchMethods
|
||||
def search(params)
|
||||
q = super
|
||||
|
||||
if params["#{model_type}_id"].present?
|
||||
q = q.where("#{model_type}_id = ?", params["#{model_type}_id"])
|
||||
end
|
||||
|
||||
if params[:user_name].present?
|
||||
user_id = User.name_to_id(params[:user_name])
|
||||
if user_id
|
||||
q = q.where("user_id = ?", user_id)
|
||||
else
|
||||
q = q.none
|
||||
end
|
||||
end
|
||||
|
||||
if params[:user_id].present?
|
||||
q = q.where("user_id = ?", params[:user_id].to_i)
|
||||
end
|
||||
|
||||
allow_complex_params = (params.keys & ["#{model_type}_id", "user_name", "user_id"]).any?
|
||||
|
||||
if allow_complex_params
|
||||
if params[:"#{model_type}_creator_name"].present?
|
||||
creator_id = User.name_to_id(params[:"#{model_type}_creator_name"])
|
||||
if creator_id
|
||||
q = q.joins(model_type).where(model_type => { "#{model_creator_column}_id": creator_id })
|
||||
else
|
||||
q = q.none
|
||||
end
|
||||
end
|
||||
|
||||
if params[:timeframe].present?
|
||||
q = q.where("#{table_name}.updated_at >= ?", params[:timeframe].to_i.days.ago)
|
||||
end
|
||||
|
||||
if params[:user_ip_addr].present?
|
||||
q = q.where("user_ip_addr <<= ?", params[:user_ip_addr])
|
||||
end
|
||||
|
||||
if params[:score].present?
|
||||
q = q.where("#{table_name}.score = ?", params[:score])
|
||||
end
|
||||
|
||||
if params[:duplicates_only] == "1"
|
||||
subselect = search(params.except("duplicates_only")).select(:user_ip_addr).group(:user_ip_addr).having("count(user_ip_addr) > 1").reorder("")
|
||||
q = q.where(user_ip_addr: subselect)
|
||||
end
|
||||
end
|
||||
|
||||
if params[:order] == "ip_addr" && allow_complex_params
|
||||
q = q.order(:user_ip_addr)
|
||||
else
|
||||
q = q.apply_default_order(params)
|
||||
end
|
||||
q
|
||||
end
|
||||
end
|
||||
|
||||
extend SearchMethods
|
||||
end
|
@ -1,74 +1 @@
|
||||
<div id="c-comment-votes">
|
||||
<div id="a-index">
|
||||
<%# path is a string here because of duplicate routes %>
|
||||
<%= form_search(path: "comment_votes") do |f| %>
|
||||
<%= f.input :user_name, label: "Voter Username", autocomplete: "user" %>
|
||||
<%= f.input :comment_id, label: "Comment ID" %>
|
||||
<br>
|
||||
<%= f.input :comment_creator_name, label: "Comment Creator Username", autocomplete: "user" %>
|
||||
<%= f.input :timeframe, label: "Timeframe", include_blank: true, collection: [["Last Week", "7"], ["Last Month", "30"], ["Last Three Months", "90"], ["Last Year", "360"]] %>
|
||||
<%= f.input :score, label: "Type", include_blank: true, collection: [["Upvote", "1"], ["Locked", "0"], ["Downvote", "-1"]] %>
|
||||
<%= f.input :user_ip_addr, label: "IP Address" %>
|
||||
<%= f.input :duplicates_only, label: "Duplicates Only", as: :boolean %>
|
||||
<%= f.input :order, collection: [["Created", "id"], ["IP Address", "ip_addr"]] %>
|
||||
<%= f.submit "Search" %>
|
||||
<% end %>
|
||||
|
||||
<table class="striped" id='votes'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Comment</th>
|
||||
<th>Comment Creator</th>
|
||||
<th>Voter</th>
|
||||
<th>Email</th>
|
||||
<th>Signed Up</th>
|
||||
<th>Vote</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @comment_votes.each do |vote| %>
|
||||
<tr id="r<%= vote.id %>">
|
||||
<td><%= vote.id %></td>
|
||||
<td><%= link_to vote.comment_id, comment_path(vote.comment) %></td>
|
||||
<td><%= mod_link_to_user vote.comment.creator, :negative %></td>
|
||||
<td><%= mod_link_to_user vote.user, :negative %></td>
|
||||
<td><%= vote.user.email %>
|
||||
<td title="Signed up at <%= vote.user.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.user.created_at) %> ago
|
||||
<td>
|
||||
<% if vote.score == 1 %><span class='greentext'>Up</span>
|
||||
<% elsif vote.score == 0 %><span class='yellowtext'>Locked</span>
|
||||
<% elsif vote.score == nil %>Unrecorded
|
||||
<% else %><span class='redtext'>Down</span>
|
||||
<% end %></td>
|
||||
<td title="Created at <%= vote.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.created_at) %> ago
|
||||
</td>
|
||||
<td title="Updated at <%= vote.updated_at.strftime("%c") %>"><%= time_ago_in_words(vote.updated_at) %> ago
|
||||
</td>
|
||||
<td><%= link_to_ip vote.user_ip_addr %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
<%= tag.button "Select All", id: "select-all-votes" %><br/>
|
||||
<%= tag.button "Lock Votes", id: "lock-votes" %> Set the votes to 0, preventing the user
|
||||
from voting on the image again<br/>
|
||||
<%= tag.button "Delete Votes", id: "delete-votes" %> Remove the votes
|
||||
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
new Danbooru.VoteManager('comment');
|
||||
<% end -%>
|
||||
|
||||
<div id="paginator">
|
||||
<%= numbered_paginator(@comment_votes) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
Comment Votes
|
||||
<% end %>
|
||||
<%= render "user_votes/common_index", type: CommentVote, votes: @comment_votes %>
|
||||
|
@ -3,13 +3,7 @@
|
||||
<% if comment.post.present? && (CurrentUser.is_moderator? || !comment.is_hidden?) %>
|
||||
<div class="comment-post">
|
||||
<div class="post-container">
|
||||
<%= content_tag(:div, { id: "post_#{comment.post.id}", class: ["post", *PostPresenter.preview_class(comment.post)].join(" ") }.merge(PostPresenter.data_attributes(comment.post))) do %>
|
||||
<div class="preview">
|
||||
<% if comment.post.visible? %>
|
||||
<%= link_to(image_tag(comment.post.preview_file_url), post_path(comment.post)) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= PostPresenter.preview(comment.post, inline: true, show_deleted: true) %>
|
||||
</div>
|
||||
<div class="comments list-of-comments">
|
||||
<%= render "comments/partials/show/comment", comment: comment, post: nil %>
|
||||
|
@ -13,13 +13,7 @@
|
||||
<% if CurrentUser.is_moderator? || post.comments.undeleted.exists? %>
|
||||
<div class="comment-post">
|
||||
<div class="post-container">
|
||||
<%= content_tag(:div, { id: "post_#{post.id}", class: ["post", *PostPresenter.preview_class(post)].join(" ") }.merge(PostPresenter.data_attributes(post))) do %>
|
||||
<div class="preview">
|
||||
<% if post.visible? %>
|
||||
<%= link_to(image_tag(post.preview_file_url), post_path(post)) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= PostPresenter.preview(post, inline: true, show_deleted: true) %>
|
||||
<div class="post-information">
|
||||
<%= render "comments/partials/index/header", :post => post %>
|
||||
</div>
|
||||
|
@ -1,14 +1,6 @@
|
||||
<div id="c-comments">
|
||||
<div id="a-show">
|
||||
<% if @comment.post %>
|
||||
<%= content_tag(:div, { id: "post_#{@comment.post_id}", class: ["post", *PostPresenter.preview_class(@comment.post)].join(" ") }.merge(PostPresenter.data_attributes(@comment.post))) do %>
|
||||
<div class="preview">
|
||||
<% if @comment.post.visible? %>
|
||||
<%= link_to(image_tag(@comment.post.preview_file_url), post_path(@comment.post)) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= PostPresenter.preview(@comment.post, inline: true, show_deleted: true) %>
|
||||
<div class="comments-for-post">
|
||||
<div class="list-of-comments">
|
||||
<%= render "comments/partials/show/comment", comment: @comment, post: nil %>
|
||||
|
91
app/views/layouts/_head.html.erb
Normal file
91
app/views/layouts/_head.html.erb
Normal file
@ -0,0 +1,91 @@
|
||||
<title><%= get_title %></title>
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00549e">
|
||||
<meta name="theme-color" content="#00549e">
|
||||
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
|
||||
<link rel="top" title="<%= Danbooru.config.app_name %>" href="/">
|
||||
<%= csrf_meta_tag %>
|
||||
<% unless disable_mobile_mode? %>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<% end %>
|
||||
|
||||
<meta name="current-user-name" content="<%= CurrentUser.name %>">
|
||||
<meta name="current-user-id" content="<%= CurrentUser.id %>">
|
||||
<meta name="current-user-can-approve-posts" content="<%= CurrentUser.can_approve_posts? %>">
|
||||
<meta name="user-comment-threshold" content="<%= CurrentUser.comment_threshold %>">
|
||||
<% if CurrentUser.user.blacklisted_tags.present? %>
|
||||
<meta name="blacklisted-tags" content="<%= CurrentUser.user.blacklisted_tags.split(/(?:\r|\n)+/).to_json %>">
|
||||
<meta name="blacklist-users" content="<%= CurrentUser.blacklist_users? %>">
|
||||
<% end %>
|
||||
<meta name="enable-js-navigation" content="<%= CurrentUser.user.enable_keyboard_navigation %>">
|
||||
<meta name="enable-auto-complete" content="<%= CurrentUser.user.enable_auto_complete %>">
|
||||
<meta name="style-usernames" content="<%= CurrentUser.user.style_usernames? %>">
|
||||
<meta name="last-forum-read-at" content="<%= CurrentUser.user.last_forum_read_at %>">
|
||||
<% if CurrentUser.user.custom_style.present? %>
|
||||
<%= stylesheet_link_tag custom_style_users_path(md5: Digest::MD5.hexdigest(CurrentUser.user.custom_style)), media: "screen", nonce: true %>
|
||||
<% end %>
|
||||
|
||||
<% if flash[:notice] =~ /error/i %>
|
||||
<meta name="errors" content="true">
|
||||
<% end %>
|
||||
<%= auto_discovery_link_tag :atom, posts_path(:format => "atom", :tags => params[:tags]) %>
|
||||
|
||||
<%= javascript_include_tag "/vendor/jquery-3.5.0.min.js", nonce: true, integrity: "sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=", crossorigin: "anonymous" %>
|
||||
<%= stylesheet_link_tag "/vendor/fontawesome/css/all.min.css", nonce: true, integrity: "sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf", crossorigin: "anonymous" %>
|
||||
<%= stylesheet_pack_tag "application", nonce: true %>
|
||||
<%= javascript_pack_tag "application", nonce: true, defer: false %>
|
||||
|
||||
<% if Danbooru.config.twitter_site %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context" : "http://schema.org",
|
||||
"@type" : "Organization",
|
||||
"name" : "<%= Danbooru.config.app_name %>",
|
||||
"url" : "<%= root_url %>",
|
||||
"sameAs" : [
|
||||
"https://twitter.com/<%= Danbooru.config.twitter_site[1..-1] %>"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"url" : "<%= root_url %>",
|
||||
"potentialAction": [{
|
||||
"@type": "SearchAction",
|
||||
"target": "<%= posts_url %>?tags={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "<%= Danbooru.config.app_name %>",
|
||||
"alternateName": "<%= Danbooru.config.description %>",
|
||||
"url" : "<%= root_url %>"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style id="blacklisted-hider">
|
||||
.post-preview, #image-container, #c-comments .post, .post-thumbnail {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
</style>
|
||||
<noscript>
|
||||
<style>
|
||||
.post-preview, #image-container, #c-comments .post, .post-thumbnail {
|
||||
visibility: visible !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
|
||||
<%= raw Danbooru.config.custom_html_header_content %>
|
||||
<%= yield :html_header %>
|
@ -1,25 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= get_title %></title>
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="top" title="<%= Danbooru.config.app_name %>" href="/">
|
||||
<%= csrf_meta_tag %>
|
||||
<% unless disable_mobile_mode? %>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<% end %>
|
||||
<%= auto_discovery_link_tag :atom, posts_path(:format => "atom", :tags => params[:tags]) %>
|
||||
<%= raw Danbooru.config.custom_html_header_content %>
|
||||
<script src="/vendor/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="/vendor/fontawesome/css/all.min.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
|
||||
<%= stylesheet_pack_tag "application", nonce: true %>
|
||||
<%= javascript_pack_tag "application", nonce: true %>
|
||||
<%= yield :html_header %>
|
||||
<%= render "layouts/head" %>
|
||||
</head>
|
||||
<body lang="en">
|
||||
<%= tag.body **body_attributes(CurrentUser.user) do %>
|
||||
<%= render "layouts/theme_include" %>
|
||||
<div id="page">
|
||||
<%= yield :layout %>
|
||||
</div>
|
||||
</body>
|
||||
<% end %>
|
||||
</html>
|
||||
|
@ -1,91 +1,7 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= get_title %></title>
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00549e">
|
||||
<meta name="theme-color" content="#00549e">
|
||||
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
|
||||
<link rel="top" title="<%= Danbooru.config.app_name %>" href="/">
|
||||
<%= csrf_meta_tag %>
|
||||
<% unless disable_mobile_mode? %>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<% end %>
|
||||
<meta name="current-user-name" content="<%= CurrentUser.name %>">
|
||||
<meta name="current-user-id" content="<%= CurrentUser.id %>">
|
||||
<meta name="current-user-can-approve-posts" content="<%= CurrentUser.can_approve_posts? %>">
|
||||
<meta name="user-comment-threshold" content="<%= CurrentUser.comment_threshold %>">
|
||||
<% if CurrentUser.user.blacklisted_tags.present? %>
|
||||
<meta name="blacklisted-tags" content="<%= CurrentUser.user.blacklisted_tags.split(/(?:\r|\n)+/).to_json %>">
|
||||
<meta name="blacklist-users" content="<%= CurrentUser.blacklist_users? %>">
|
||||
<% end %>
|
||||
<% if flash[:notice] =~ /error/i %>
|
||||
<meta name="errors" content="true">
|
||||
<% end %>
|
||||
<meta name="enable-js-navigation" content="<%= CurrentUser.user.enable_keyboard_navigation %>">
|
||||
<meta name="enable-auto-complete" content="<%= CurrentUser.user.enable_auto_complete %>">
|
||||
<meta name="style-usernames" content="<%= CurrentUser.user.style_usernames? %>">
|
||||
<meta name="last-forum-read-at" content="<%= CurrentUser.user.last_forum_read_at %>">
|
||||
<%= auto_discovery_link_tag :atom, posts_path(:format => "atom", :tags => params[:tags]) %>
|
||||
<%= javascript_include_tag "/vendor/jquery-3.5.0.min.js", nonce: true, integrity: "sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=", crossorigin: "anonymous" %>
|
||||
<%= stylesheet_link_tag "/vendor/fontawesome/css/all.min.css", nonce: true, integrity: "sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf", crossorigin: "anonymous" %>
|
||||
<%= stylesheet_pack_tag "application", nonce: true %>
|
||||
<%= javascript_pack_tag "application", nonce: true, defer: false %>
|
||||
<% if CurrentUser.user.custom_style.present? %>
|
||||
<%= stylesheet_link_tag custom_style_users_path(md5: Digest::MD5.hexdigest(CurrentUser.user.custom_style)), media: "screen", nonce: true %>
|
||||
<% end %>
|
||||
<% if Danbooru.config.twitter_site %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context" : "http://schema.org",
|
||||
"@type" : "Organization",
|
||||
"name" : "<%= Danbooru.config.app_name %>",
|
||||
"url" : "<%= root_url %>",
|
||||
"sameAs" : [
|
||||
"https://twitter.com/<%= Danbooru.config.twitter_site[1..-1] %>"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"url" : "<%= root_url %>",
|
||||
"potentialAction": [{
|
||||
"@type": "SearchAction",
|
||||
"target": "<%= posts_url %>?tags={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "<%= Danbooru.config.app_name %>",
|
||||
"alternateName": "<%= Danbooru.config.description %>",
|
||||
"url" : "<%= root_url %>"
|
||||
}
|
||||
</script>
|
||||
<%= yield :html_header %>
|
||||
<%= raw Danbooru.config.custom_html_header_content %>
|
||||
<style id="blacklisted-hider">
|
||||
.post-preview, #image-container, #c-comments .post, .post-thumbnail {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
</style>
|
||||
<noscript>
|
||||
<style>
|
||||
.post-preview, #image-container, #c-comments .post, .post-thumbnail {
|
||||
visibility: visible !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<%= render "layouts/head" %>
|
||||
</head>
|
||||
<%= tag.body **body_attributes(CurrentUser.user) do %>
|
||||
<%= render "layouts/theme_include" %>
|
||||
@ -120,7 +36,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "news_updates/listing" %>
|
||||
<%= render "news_updates/notice", news_update: NewsUpdate.recent %>
|
||||
|
||||
<% if CurrentUser.user.is_banned? %>
|
||||
<%= render "users/ban_notice" %>
|
||||
|
11
app/views/mascots/_form.html.erb
Normal file
11
app/views/mascots/_form.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<%= custom_form_for(mascot) do |f| %>
|
||||
<%= error_messages_for "mascot" %>
|
||||
<%= f.input :mascot_file, as: :file, input_html: { accept: "image/png,image/jpeg,.png,.jpg,.jpeg" } %>
|
||||
<%= f.input :display_name %>
|
||||
<%= f.input :background_color %>
|
||||
<%= f.input :artist_url %>
|
||||
<%= f.input :artist_name %>
|
||||
<%= f.input :safe_mode_only, label: "E9 Only" %>
|
||||
<%= f.input :active %>
|
||||
<%= f.submit %>
|
||||
<% end %>
|
8
app/views/mascots/_secondary_links.html.erb
Normal file
8
app/views/mascots/_secondary_links.html.erb
Normal file
@ -0,0 +1,8 @@
|
||||
<% content_for(:secondary_links) do %>
|
||||
<menu>
|
||||
<%= subnav_link_to "Listing", mascots_path %>
|
||||
<% if CurrentUser.user.is_admin? %>
|
||||
<%= subnav_link_to "New", new_mascot_path %>
|
||||
<% end %>
|
||||
</menu>
|
||||
<% end %>
|
12
app/views/mascots/edit.html.erb
Normal file
12
app/views/mascots/edit.html.erb
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="c-mascots">
|
||||
<div class="a-edit">
|
||||
<h1>Edit Mascot</h1>
|
||||
<%= render "form", mascot: @mascot %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render "secondary_links" %>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
Edit Mascot
|
||||
<% end %>
|
49
app/views/mascots/index.html.erb
Normal file
49
app/views/mascots/index.html.erb
Normal file
@ -0,0 +1,49 @@
|
||||
<div id="c-mascots">
|
||||
<div id="a-index">
|
||||
<h1>Mascots</h1>
|
||||
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Background Color</th>
|
||||
<th>Artist Name</th>
|
||||
<th>Artist URL</th>
|
||||
<th>Active</th>
|
||||
<th>E9 Only</th>
|
||||
<th>Created</th>
|
||||
<% if CurrentUser.user.is_admin? %>
|
||||
<th></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @mascots.each do |mascot| %>
|
||||
<tr>
|
||||
<td><%= link_to mascot.display_name, mascot.url_path %></td>
|
||||
<td><%= mascot.background_color %></td>
|
||||
<td><%= mascot.artist_name %></td>
|
||||
<td><%= mascot.artist_url %></td>
|
||||
<td><%= mascot.active %></td>
|
||||
<td><%= mascot.safe_mode_only %></td>
|
||||
<td><%= compact_time mascot.created_at %></td>
|
||||
<% if CurrentUser.user.is_admin? %>
|
||||
<td>
|
||||
<%= link_to "Edit", edit_mascot_path(mascot) %>
|
||||
| <%= link_to "Delete", mascot_path(mascot), method: :delete, data: { confirm: "Are you sure you want to delete this mascot?" } %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<%= numbered_paginator(@mascots) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render "secondary_links" %>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
Mascots
|
||||
<% end %>
|
12
app/views/mascots/new.html.erb
Normal file
12
app/views/mascots/new.html.erb
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="c-mascots">
|
||||
<div class="a-new">
|
||||
<h1>New Mascot</h1>
|
||||
<%= render "form", mascot: @mascot %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render "secondary_links" %>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
New Mascot
|
||||
<% end %>
|
@ -5,6 +5,9 @@
|
||||
<%= f.input :user_id, label: "User IDs" %>
|
||||
<%= f.input :user_name, label: "User Names", autocomplete: "user" %>
|
||||
<%= f.input :ip_addr, label: "IP Addresses" %>
|
||||
<%= f.input :with_history, label: "With History", as: :boolean %>
|
||||
<% if (params.dig(:search, :ip_addr)&.split(",")&.count || 0) == 1 %>
|
||||
<%= f.input :add_ip_mask, label: "Add IP Mask?", as: :boolean, hint: "/24 for IPv4, /64 for IPv6" %>
|
||||
<% end %>
|
||||
<%= f.input :with_history, label: "With History?", as: :boolean %>
|
||||
<%= f.submit "Search" %>
|
||||
<% end %>
|
||||
|
@ -1,10 +0,0 @@
|
||||
<% if NewsUpdate.recent.present? %>
|
||||
<div class="ui-state-highlight site-notice" style="display: none;" id="news" data-id="<%= NewsUpdate.recent[0].id %>">
|
||||
<div id="news-closebutton" class="closebutton">Dismiss</div>
|
||||
<h6>News - <%= NewsUpdate.recent[0].created_at.strftime("%b %d, %Y") %>
|
||||
(<%= time_ago_in_words NewsUpdate.recent[0].created_at %> ago)
|
||||
<span id="news-showtext" class="showtext">Click to show.</span>
|
||||
</h6>
|
||||
<div class="newsbody dtext-container"><%= format_text(NewsUpdate.recent[0].message) %></div>
|
||||
</div>
|
||||
<% end %>
|
@ -1,3 +0,0 @@
|
||||
<% if NewsUpdate.recent.present? %>
|
||||
<div class="dtext-container"><%= format_text(NewsUpdate.recent[0].message) %></div>
|
||||
<% end %>
|
10
app/views/news_updates/_notice.erb
Normal file
10
app/views/news_updates/_notice.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<% if news_update.present? %>
|
||||
<div class="ui-state-highlight site-notice" style="display: none;" id="news" data-id="<%= news_update.id %>">
|
||||
<div id="news-closebutton" class="closebutton">Dismiss</div>
|
||||
<h6>News - <%= news_update.created_at.strftime("%b %d, %Y") %>
|
||||
(<%= time_ago_in_words news_update.created_at %> ago)
|
||||
<span id="news-showtext" class="showtext">Click to show.</span>
|
||||
</h6>
|
||||
<div class="newsbody dtext-container"><%= format_text(news_update.message) %></div>
|
||||
</div>
|
||||
<% end %>
|
@ -16,7 +16,7 @@
|
||||
<%= @post_set.presenter.post_previews_html(self) %>
|
||||
</div>
|
||||
|
||||
<%= numbered_paginator(@post_set) %>
|
||||
<%= numbered_paginator(@post_set.posts) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<% elsif post_version.description_changed && post_version.version != 1%>
|
||||
Cleared
|
||||
<em>Cleared</em>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pv-tags-locked pv-content">
|
||||
|
@ -1,70 +1 @@
|
||||
<div id="c-post-votes">
|
||||
<div id="a-index">
|
||||
<%# path is a string here because of duplicate routes %>
|
||||
<%= form_search(path: "post_votes") do |f| %>
|
||||
<%= f.input :user_name, label: "Username", autocomplete: "user" %>
|
||||
<%= f.input :post_id, label: "Post ID" %>
|
||||
<br>
|
||||
<%= f.input :timeframe, label: "Timeframe", include_blank: true, collection: [["Last Week", "7"], ["Last Month", "30"], ["Last Three Months", "90"], ["Last Year", "360"]] %>
|
||||
<%= f.input :score, label: "Type", include_blank: true, collection: [["Upvote", "1"], ["Locked", "0"], ["Downvote", "-1"]] %>
|
||||
<%= f.input :user_ip_addr, label: "IP Address" %>
|
||||
<%= f.input :duplicates_only, label: "Duplicates Only", as: :boolean %>
|
||||
<%= f.input :order, collection: [["Created", "id"], ["IP Address", "ip_addr"]] %>
|
||||
<%= f.submit "Search" %>
|
||||
<% end %>
|
||||
|
||||
<table class='striped' id='votes'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Post</th>
|
||||
<th>Voter</th>
|
||||
<th>Email</th>
|
||||
<th>Signed Up</th>
|
||||
<th>Vote</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @post_votes.each do |vote| %>
|
||||
<tr id="r<%= vote.id %>">
|
||||
<td><%= vote.id %></td>
|
||||
<td><%= link_to vote.post_id, post_path(id: vote.post_id) %></td>
|
||||
<td><%= mod_link_to_user vote.user, :negative %></td>
|
||||
<td><%= vote.user.email %>
|
||||
<td title="Signed up at <%= vote.user.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.user.created_at) %> ago
|
||||
<td>
|
||||
<% if vote.score == 1 %><span class='greentext'>Up</span>
|
||||
<% elsif vote.score == 0 %><span class='yellowtext'>Locked</span>
|
||||
<% else %><span class='redtext'>Down</span>
|
||||
<% end %></td>
|
||||
<td title="Created at <%= vote.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.created_at) %> ago
|
||||
</td>
|
||||
<td title="Updated at <%= vote.updated_at.strftime("%c") %>"><%= time_ago_in_words(vote.updated_at) %> ago
|
||||
</td>
|
||||
<td><%= link_to_ip vote.user_ip_addr %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% end %>
|
||||
</table>
|
||||
<br/>
|
||||
<%= tag.button "Select All", id: "select-all-votes" %><br/>
|
||||
<%= tag.button "Lock Votes", id: "lock-votes" %> Set the votes to 0, preventing the user from
|
||||
voting on the image again<br/>
|
||||
<%= tag.button "Delete Votes", id: "delete-votes" %> Remove the votes
|
||||
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
new Danbooru.VoteManager('post');
|
||||
<% end -%>
|
||||
|
||||
<div id="paginator">
|
||||
<%= numbered_paginator(@post_votes) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
Post Votes
|
||||
<% end %>
|
||||
<%= render "user_votes/common_index", type: PostVote, votes: @post_votes %>
|
||||
|
@ -32,6 +32,10 @@
|
||||
}
|
||||
|
||||
.mascotbox {
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
background-repeat:no-repeat;
|
||||
background-attachment:fixed;
|
||||
background-position:50% 0;
|
||||
@ -44,6 +48,23 @@
|
||||
text-shadow: 0 0 2px black, 0 0 6px black;
|
||||
}
|
||||
|
||||
.mascotbox:before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
|
||||
background-image: inherit;
|
||||
background-color: inherit;
|
||||
background-repeat: inherit;
|
||||
background-attachment: inherit;
|
||||
background-position: inherit;
|
||||
filter: blur(8px);
|
||||
}
|
||||
|
||||
#searchbox {
|
||||
padding-bottom:5px;
|
||||
}
|
||||
@ -66,7 +87,7 @@
|
||||
<div id="c-static">
|
||||
<div id="a-home">
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
var mascots = <%= Danbooru.config.mascots.to_json.html_safe %>;
|
||||
var mascots = <%= Mascot.active_for_browser.to_json.html_safe %>;
|
||||
<% end -%>
|
||||
|
||||
<div id="searchbox" class='mascotbox'>
|
||||
@ -81,7 +102,7 @@
|
||||
<%= link_to "Posts", posts_path, title: "A paginated list of every post" %>
|
||||
<%= link_to "Comments", comments_path, title: "A paginated list of every comment" %>
|
||||
<%= link_to "Tags", tags_path, title: "A paginated list of every tag" %>
|
||||
<%= link_to "Wiki", wiki_pages_path, title: "Wiki" %>
|
||||
<%= link_to "Wiki", wiki_pages_path(title: 'help:home'), title: "Wiki" %>
|
||||
<%= link_to "Forum", forum_topics_path, title: "Forum" %>
|
||||
<%= link_to "»".html_safe, site_map_path, title: "A site map" %>
|
||||
</div>
|
||||
@ -104,9 +125,9 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if NewsUpdate.recent.present? %>
|
||||
<% if news_update = NewsUpdate.recent %>
|
||||
<div id="news-excerpt-box" class="mascotbox">
|
||||
<div class="news-excerpt dtext-container"><%= format_text(NewsUpdate.recent[0].message.lines.first, inline: true) %></div>
|
||||
<div class="news-excerpt dtext-container"><%= format_text(news_update.message.lines.first, inline: true) %></div>
|
||||
<div class="previous-news-link"><%= link_to "Click here for previous news", news_updates_path %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -30,6 +30,7 @@
|
||||
<ul>
|
||||
<li><h1>Tools</h1></li>
|
||||
<li><%= link_to("News Updates", news_updates_path) %></li>
|
||||
<li><%= link_to("Mascots", mascots_path) %></li>
|
||||
<li><%= link_to("Source Code", Danbooru.config.source_code_url) %></li>
|
||||
<li><%= link_to("Keyboard Shortcuts", keyboard_shortcuts_path) %></li>
|
||||
<li><%= link_to("API Documentation", help_page_path(id: "api")) %></li>
|
||||
|
70
app/views/user_votes/_common_index.html.erb
Normal file
70
app/views/user_votes/_common_index.html.erb
Normal file
@ -0,0 +1,70 @@
|
||||
<div id="c-<%= type.model_name.plural %>">
|
||||
<div id="a-index">
|
||||
<%# path is a string here because of duplicate routes %>
|
||||
<%= form_search(path: type.model_name.route_key) do |f| %>
|
||||
<%= f.input :user_name, label: "Voter Username", autocomplete: "user" %>
|
||||
<%= f.input :"#{type.model_type}_id", label: "#{type.model_type.capitalize} ID" %>
|
||||
<br>
|
||||
<%= f.input :"#{type.model_type}_creator_name", label: "#{type.model_type.capitalize} Creator Username", autocomplete: "user" %>
|
||||
<%= f.input :timeframe, label: "Timeframe", include_blank: true, collection: [["Last Week", "7"], ["Last Month", "30"], ["Last Three Months", "90"], ["Last Year", "360"]] %>
|
||||
<%= f.input :score, label: "Type", include_blank: true, collection: [["Upvote", "1"], ["Locked", "0"], ["Downvote", "-1"]] %>
|
||||
<%= f.input :user_ip_addr, label: "IP Address" %>
|
||||
<%= f.input :duplicates_only, label: "Duplicates Only", as: :boolean %>
|
||||
<%= f.input :order, collection: [["Created", "id"], ["IP Address", "ip_addr"]] %>
|
||||
<%= f.submit "Search" %>
|
||||
<% end %>
|
||||
|
||||
<table class="striped" id='votes'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><%= type.model_type.capitalize %></th>
|
||||
<th><%= type.model_type.capitalize %> Creator</th>
|
||||
<th>Voter</th>
|
||||
<th>Email</th>
|
||||
<th>Signed Up</th>
|
||||
<th>Vote</th>
|
||||
<th>Created</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% votes.each do |vote| %>
|
||||
<tr id="r<%= vote.id %>">
|
||||
<td><%= vote.id %></td>
|
||||
<td><%= link_to vote.send("#{type.model_type}_id"), vote.send(type.model_type) %></td>
|
||||
<td><%= mod_link_to_user vote.send(type.model_type).send(type.model_creator_column), :negative %></td>
|
||||
<td><%= mod_link_to_user vote.user, :negative %></td>
|
||||
<td><%= vote.user.email %>
|
||||
<td title="Signed up at <%= vote.user.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.user.created_at) %> ago
|
||||
<td>
|
||||
<% if vote.is_positive? %><span class='greentext'>Up</span>
|
||||
<% elsif vote.is_locked? %><span class='yellowtext'>Locked</span>
|
||||
<% else %><span class='redtext'>Down</span>
|
||||
<% end %></td>
|
||||
<td title="Created at <%= vote.created_at.strftime("%c") %>"><%= time_ago_in_words(vote.created_at) %> ago
|
||||
</td>
|
||||
<td><%= link_to_ip vote.user_ip_addr %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
<%= tag.button "Select All", id: "select-all-votes" %><br/>
|
||||
<%= tag.button "Lock Votes", id: "lock-votes" %> Set the votes to 0, preventing the user
|
||||
from voting on the <%= type.model_type %> again<br/>
|
||||
<%= tag.button "Delete Votes", id: "delete-votes" %> Remove the votes
|
||||
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
new Danbooru.VoteManager('<%= type.model_type %>');
|
||||
<% end -%>
|
||||
|
||||
<div id="paginator">
|
||||
<%= numbered_paginator(votes) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% content_for(:page_title) do %>
|
||||
<%= type.model_name.plural.titleize %>
|
||||
<% end %>
|
@ -352,8 +352,8 @@ fart'
|
||||
def max_file_sizes
|
||||
{
|
||||
'jpg' => 100.megabytes,
|
||||
'gif' => 20.megabytes,
|
||||
'png' => 100.megabytes,
|
||||
'gif' => 20.megabytes,
|
||||
'webm' => 100.megabytes
|
||||
}
|
||||
end
|
||||
@ -600,6 +600,8 @@ fart'
|
||||
"Previously deleted (post #%PARENT_ID%)",
|
||||
"Excessive same base image set",
|
||||
"Colored base",
|
||||
"Advertisment",
|
||||
"Underage artist",
|
||||
"",
|
||||
"Does not meet minimum quality standards (Artistic)",
|
||||
"Does not meet minimum quality standards (Resolution)",
|
||||
@ -614,6 +616,7 @@ fart'
|
||||
"Irrelevant to site (Human only)",
|
||||
"Irrelevant to site (Screencap)",
|
||||
"Irrelevant to site (Zero pictured)",
|
||||
"Irrelevant to site (AI assisted/generated)",
|
||||
"Irrelevant to site (%OTHER_ID%)",
|
||||
"",
|
||||
"Paysite/commercial content",
|
||||
@ -783,19 +786,6 @@ fart'
|
||||
{zone: nil, revive_id: nil, checksum: nil}
|
||||
end
|
||||
|
||||
def mascots
|
||||
[
|
||||
["https://static1.e621.net/data/mascot_bg/esix1.jpg", "#012e56", "<a href='http://www.furaffinity.net/user/keishinkae'>Keishinkae</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/esix2.jpg", "#012e56", "<a href='http://www.furaffinity.net/user/keishinkae'>Keishinkae</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/raptor1.jpg", "#012e56", "<a href='http://nowhereincoming.net/'>darkdoomer</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/hexerade.jpg", "#002d55", "<a href='http://www.furaffinity.net/user/chizi'>chizi</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/wiredhooves.jpg", "#012e56", "<a href='http://www.furaffinity.net/user/wiredhooves'>wiredhooves</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/ecmajor.jpg", "#012e57", "<a href='http://www.horsecore.org/'>ECMajor</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/evalionfix.jpg", "#012e57", "<a href='http://www.furaffinity.net/user/evalion'>evalion</a>"],
|
||||
["https://static1.e621.net/data/mascot_bg/peacock.png", "#012e57", "<a href='http://www.furaffinity.net/user/ratte'>Ratte</a>"]
|
||||
]
|
||||
end
|
||||
|
||||
# Additional video samples will be generated in these dimensions if it makes sense to do so
|
||||
# They will be available as additional scale options on applicable posts in the order they appear here
|
||||
def video_rescales
|
||||
|
@ -341,6 +341,7 @@ Rails.application.routes.draw do
|
||||
get :resend_confirmation
|
||||
end
|
||||
end
|
||||
resources :mascots, only: [:index, :new, :create, :edit, :update, :destroy]
|
||||
|
||||
options "*all", to: "application#enable_cors"
|
||||
|
||||
|
16
db/migrate/20221014085948_add_mascot_table.rb
Normal file
16
db/migrate/20221014085948_add_mascot_table.rb
Normal file
@ -0,0 +1,16 @@
|
||||
class AddMascotTable < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
create_table :mascots do |t|
|
||||
t.references :creator, foreign_key: { to_table: :users }, null: false
|
||||
t.string :display_name, null: false
|
||||
t.string :md5, index: { unique: true }, null: false
|
||||
t.string :file_ext, null: false
|
||||
t.string :background_color, null: false
|
||||
t.string :artist_url, null: false
|
||||
t.string :artist_name, null: false
|
||||
t.boolean :safe_mode_only, default: false, null: false
|
||||
t.boolean :active, default: true, null: false
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
68
db/seeds.rb
68
db/seeds.rb
@ -13,7 +13,7 @@ end
|
||||
puts "== Seeding database with sample content ==\n"
|
||||
|
||||
# Uncomment to see detailed logs
|
||||
#ActiveRecord::Base.logger = ActiveSupport::Logger.new($stdout)
|
||||
# ActiveRecord::Base.logger = ActiveSupport::Logger.new($stdout)
|
||||
|
||||
admin = User.find_or_create_by!(name: "admin") do |user|
|
||||
user.created_at = 2.weeks.ago
|
||||
@ -41,41 +41,57 @@ ForumCategory.find_or_create_by!(id: Danbooru.config.alias_implication_forum_cat
|
||||
category.can_view = 0
|
||||
end
|
||||
|
||||
unless Rails.env.test?
|
||||
CurrentUser.user = admin
|
||||
CurrentUser.ip_addr = "127.0.0.1"
|
||||
|
||||
resources = YAML.load_file Rails.root.join("db", "seeds.yml")
|
||||
url = "https://e621.net/posts.json?limit=#{ENV.fetch("SEED_POST_COUNT", 100)}&tags=id:#{resources["post_ids"].join(",")}"
|
||||
response = HTTParty.get(url, {
|
||||
headers: {"User-Agent" => "e621ng/seeding"}
|
||||
def api_request(path)
|
||||
response = HTTParty.get("https://e621.net#{path}", {
|
||||
headers: { "User-Agent" => "e621ng/seeding" },
|
||||
})
|
||||
json = JSON.parse(response.body)
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
def import_posts
|
||||
resources = YAML.load_file Rails.root.join("db/seeds.yml")
|
||||
json = api_request("/posts.json?limit=#{ENV.fetch('SEED_POST_COUNT', 100)}&tags=id:#{resources['post_ids'].join(',')}")
|
||||
|
||||
json["posts"].each do |post|
|
||||
puts post["file"]["url"]
|
||||
|
||||
data = Net::HTTP.get(URI(post["file"]["url"]))
|
||||
file = Tempfile.new.binmode
|
||||
file.write data
|
||||
|
||||
post["tags"].each do |category, tags|
|
||||
Tag.find_or_create_by_name_list(tags.map {|tag| category + ":" + tag})
|
||||
Tag.find_or_create_by_name_list(tags.map { |tag| "#{category}:#{tag}" })
|
||||
end
|
||||
|
||||
md5 = Digest::MD5.hexdigest(data)
|
||||
service = UploadService.new({
|
||||
uploader_id: CurrentUser.id,
|
||||
uploader_ip_addr: CurrentUser.ip_addr,
|
||||
file: file,
|
||||
tag_string: post["tags"].values.flatten.join(" "),
|
||||
source: post["sources"].join("\n"),
|
||||
description: post["description"],
|
||||
rating: post["rating"],
|
||||
md5: md5,
|
||||
md5_confirmation: md5
|
||||
})
|
||||
uploader: CurrentUser.user,
|
||||
uploader_ip_addr: CurrentUser.ip_addr,
|
||||
direct_url: post["file"]["url"],
|
||||
tag_string: post["tags"].values.flatten.join(" "),
|
||||
source: post["sources"].join("\n"),
|
||||
description: post["description"],
|
||||
rating: post["rating"],
|
||||
})
|
||||
|
||||
service.start!
|
||||
end
|
||||
end
|
||||
|
||||
def import_mascots
|
||||
api_request("/mascots.json").each do |mascot|
|
||||
puts mascot["url_path"]
|
||||
Mascot.create!(
|
||||
creator: CurrentUser.user,
|
||||
mascot_file: Downloads::File.new(mascot["url_path"]).download!,
|
||||
display_name: mascot["display_name"],
|
||||
background_color: mascot["background_color"],
|
||||
artist_url: mascot["artist_url"],
|
||||
artist_name: mascot["artist_name"],
|
||||
safe_mode_only: mascot["safe_mode_only"],
|
||||
active: mascot["active"],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
unless Rails.env.test?
|
||||
CurrentUser.user = admin
|
||||
CurrentUser.ip_addr = "127.0.0.1"
|
||||
import_posts
|
||||
import_mascots
|
||||
end
|
||||
|
@ -1034,6 +1034,45 @@ CREATE SEQUENCE public.janitor_trials_id_seq
|
||||
ALTER SEQUENCE public.janitor_trials_id_seq OWNED BY public.janitor_trials.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.mascots (
|
||||
id bigint NOT NULL,
|
||||
creator_id bigint NOT NULL,
|
||||
display_name character varying NOT NULL,
|
||||
md5 character varying NOT NULL,
|
||||
file_ext character varying NOT NULL,
|
||||
background_color character varying NOT NULL,
|
||||
artist_url character varying NOT NULL,
|
||||
artist_name character varying NOT NULL,
|
||||
safe_mode_only boolean DEFAULT false NOT NULL,
|
||||
active boolean DEFAULT true NOT NULL,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.mascots_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.mascots_id_seq OWNED BY public.mascots.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: mod_actions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
@ -2623,6 +2662,13 @@ ALTER TABLE ONLY public.ip_bans ALTER COLUMN id SET DEFAULT nextval('public.ip_b
|
||||
ALTER TABLE ONLY public.janitor_trials ALTER COLUMN id SET DEFAULT nextval('public.janitor_trials_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.mascots ALTER COLUMN id SET DEFAULT nextval('public.mascots_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: news_updates id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@ -3076,6 +3122,14 @@ ALTER TABLE ONLY public.janitor_trials
|
||||
ADD CONSTRAINT janitor_trials_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots mascots_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.mascots
|
||||
ADD CONSTRAINT mascots_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: mod_actions mod_actions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -3765,6 +3819,20 @@ CREATE UNIQUE INDEX index_ip_bans_on_ip_addr ON public.ip_bans USING btree (ip_a
|
||||
CREATE INDEX index_janitor_trials_on_user_id ON public.janitor_trials USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_mascots_on_creator_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_mascots_on_creator_id ON public.mascots USING btree (creator_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_mascots_on_md5; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX index_mascots_on_md5 ON public.mascots USING btree (md5);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_mod_actions_on_action; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@ -4452,6 +4520,14 @@ ALTER TABLE ONLY public.staff_audit_logs
|
||||
ADD CONSTRAINT fk_rails_02329e5ef9 FOREIGN KEY (user_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: mascots fk_rails_9901e810fa; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.mascots
|
||||
ADD CONSTRAINT fk_rails_9901e810fa FOREIGN KEY (creator_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: favorites fk_rails_a7668ef613; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -4734,6 +4810,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20220316162257'),
|
||||
('20220516103329'),
|
||||
('20220710133556'),
|
||||
('20220810131625');
|
||||
('20220810131625'),
|
||||
('20221014085948');
|
||||
|
||||
|
||||
|
@ -13,7 +13,10 @@ x-environment: &common-env
|
||||
|
||||
services:
|
||||
e621:
|
||||
build: ./
|
||||
build:
|
||||
context: ./
|
||||
args:
|
||||
COMPOSE_PROFILES: ${COMPOSE_PROFILES-}
|
||||
image: e621
|
||||
volumes:
|
||||
- .:/app
|
||||
@ -31,24 +34,6 @@ services:
|
||||
- iqdb
|
||||
tty: true
|
||||
|
||||
tests:
|
||||
image: e621
|
||||
environment:
|
||||
<<: *common-env
|
||||
# Hide annoying output from libvips on corrupt files
|
||||
VIPS_WARNING: "0"
|
||||
volumes:
|
||||
- .:/app
|
||||
- node_modules:/app/node_modules
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- memcached
|
||||
- elastic
|
||||
entrypoint: ["docker/test_runner.sh"]
|
||||
profiles:
|
||||
- test
|
||||
|
||||
nginx:
|
||||
image: nginx:stable-alpine
|
||||
volumes:
|
||||
@ -111,6 +96,45 @@ services:
|
||||
- post_data:/data
|
||||
- iqdb_data:/home/vagrant/iqdbs
|
||||
|
||||
# Useful for development
|
||||
|
||||
tests:
|
||||
image: e621
|
||||
environment:
|
||||
<<: *common-env
|
||||
# Hide annoying output from libvips on corrupt files
|
||||
VIPS_WARNING: "0"
|
||||
volumes:
|
||||
- .:/app
|
||||
- node_modules:/app/node_modules
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- memcached
|
||||
- elastic
|
||||
entrypoint: ["docker/test_runner.sh"]
|
||||
profiles:
|
||||
- tests
|
||||
|
||||
rubocop:
|
||||
image: e621
|
||||
volumes:
|
||||
- .:/app
|
||||
entrypoint: bundle exec rubocop
|
||||
profiles:
|
||||
- rubocop
|
||||
|
||||
solargraph:
|
||||
image: e621
|
||||
entrypoint: solargraph socket -h 0.0.0.0
|
||||
working_dir: $PWD
|
||||
volumes:
|
||||
- .:$PWD
|
||||
ports:
|
||||
- 7658:7658
|
||||
profiles:
|
||||
- solargraph
|
||||
|
||||
volumes:
|
||||
post_data:
|
||||
iqdb_data:
|
||||
|
@ -42,6 +42,26 @@ class Admin::UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal(50, @admin.level)
|
||||
end
|
||||
end
|
||||
|
||||
context "on an user with a blank email" do
|
||||
setup do
|
||||
@user = create(:user, email: "")
|
||||
Danbooru.config.stubs(:enable_email_verification?).returns(true)
|
||||
end
|
||||
|
||||
should "succeed" do
|
||||
put_auth admin_user_path(@user), @mod, params: { user: { level: "20", email: "" } }
|
||||
assert_redirected_to(user_path(@user))
|
||||
@user.reload
|
||||
assert_equal(20, @user.level)
|
||||
end
|
||||
|
||||
should "prevent invalid emails" do
|
||||
put_auth admin_user_path(@user), @mod, params: { user: { level: "10", email: "invalid" } }
|
||||
@user.reload
|
||||
assert_equal("", @user.email)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,7 @@ module Maintenance
|
||||
class EmailChangesControllerTest < ActionDispatch::IntegrationTest
|
||||
context "in all cases" do
|
||||
setup do
|
||||
Danbooru.config.stubs(:enable_email_verification?).returns(true)
|
||||
@user = create(:user, email: "bob@ogres.net")
|
||||
end
|
||||
|
||||
@ -32,6 +33,20 @@ module Maintenance
|
||||
assert_equal("bob@ogres.net", @user.email)
|
||||
end
|
||||
end
|
||||
|
||||
should "not work with an invalid email" do
|
||||
post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "password", email: "" } }
|
||||
@user.reload
|
||||
assert_not_equal("", @user.email)
|
||||
assert_match(/Email can't be blank/, flash[:notice])
|
||||
end
|
||||
|
||||
should "work with a valid email when the users current email is invalid" do
|
||||
@user = create(:user, email: "")
|
||||
post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "password", email: "abc@ogres.net" } }
|
||||
@user.reload
|
||||
assert_equal("abc@ogres.net", @user.email)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -12,14 +12,9 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
should "list all users for /users?name=<name>" do
|
||||
get users_path, params: { name: @user.name }
|
||||
assert_redirected_to(@user)
|
||||
end
|
||||
|
||||
should "raise error for /users?name=<nonexistent>" do
|
||||
get users_path, params: { name: "nobody" }
|
||||
assert_response :error
|
||||
should "redirect for /users?name=<name>" do
|
||||
get users_path, params: { name: "some_username" }
|
||||
assert_redirected_to(user_path(id: "some_username"))
|
||||
end
|
||||
|
||||
should "list all users (with search)" do
|
||||
@ -96,6 +91,43 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a duplicate username" do
|
||||
setup do
|
||||
create(:user, name: "test123")
|
||||
end
|
||||
|
||||
should "prevent creation" do
|
||||
assert_no_difference(-> { User.count }) do
|
||||
post users_path, params: { user: { name: "TEst123", password: "xxxxx1", password_confirmation: "xxxxx1" } }
|
||||
assert_match(/Name already exists/, flash[:notice])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with email validation" do
|
||||
setup do
|
||||
Danbooru.config.stubs(:enable_email_verification?).returns(true)
|
||||
end
|
||||
|
||||
should "reject invalid emails" do
|
||||
assert_no_difference(-> { User.count }) do
|
||||
post users_path, params: { user: { name: "test", password: "xxxxxx", password_confirmation: "xxxxxx" } }
|
||||
assert_match(/Email can't be blank/, flash[:notice])
|
||||
post users_path, params: { user: { name: "test", password: "xxxxxx", password_confirmation: "xxxxxx", email: "invalid" } }
|
||||
assert_match(/Email is invalid/, flash[:notice])
|
||||
end
|
||||
end
|
||||
|
||||
should "reject duplicate emails" do
|
||||
create(:user, email: "valid@e621.net")
|
||||
|
||||
assert_no_difference(-> { User.count }) do
|
||||
post users_path, params: { user: { name: "test2", password: "xxxxxx", password_confirmation: "xxxxxx", email: "VaLid@E621.net" } }
|
||||
assert_match(/Email has already been taken/, flash[:notice])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "edit action" do
|
||||
@ -131,6 +163,18 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal(20, @user.level)
|
||||
end
|
||||
end
|
||||
|
||||
context "for an user with blank email" do
|
||||
setup do
|
||||
@user = create(:user, email: "")
|
||||
Danbooru.config.stubs(:enable_email_verification?).returns(true)
|
||||
end
|
||||
|
||||
should "force them to update their email" do
|
||||
put_auth user_path(@user), @user, params: { user: { comment_threshold: "-100" } }
|
||||
assert_match(/Email can't be blank/, flash[:notice])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -197,33 +197,33 @@ class ArtistTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "search on its name should return results" do
|
||||
artist = FactoryBot.create(:artist, :name => "artist")
|
||||
FactoryBot.create(:artist, name: "artist")
|
||||
|
||||
assert_not_nil(Artist.search(:name => "artist").first)
|
||||
assert_not_nil(Artist.search(:name_like => "artist").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "artist").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "/art/").first)
|
||||
assert_not_nil(Artist.search(name: "artist").first)
|
||||
assert_not_nil(Artist.search(name_like: "artist").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "artist").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "*art*").first)
|
||||
end
|
||||
|
||||
should "search on other names should return matches" do
|
||||
artist = FactoryBot.create(:artist, :name => "artist", :other_names_string => "aaa ccc_ddd")
|
||||
FactoryBot.create(:artist, name: "artist", other_names_string: "aaa ccc_ddd")
|
||||
|
||||
assert_nil(Artist.search(any_other_name_like: "*artist*").first)
|
||||
assert_not_nil(Artist.search(any_other_name_like: "*aaa*").first)
|
||||
assert_not_nil(Artist.search(any_other_name_like: "*ccc_ddd*").first)
|
||||
assert_not_nil(Artist.search(name: "artist").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "aaa").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "/a/").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "aaa").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "*a*").first)
|
||||
end
|
||||
|
||||
should "search on group name and return matches" do
|
||||
cat_or_fish = FactoryBot.create(:artist, :name => "cat_or_fish")
|
||||
yuu = FactoryBot.create(:artist, :name => "yuu", :group_name => "cat_or_fish")
|
||||
cat_or_fish = FactoryBot.create(:artist, name: "cat_or_fish")
|
||||
FactoryBot.create(:artist, name: "yuu", group_name: "cat_or_fish")
|
||||
|
||||
assert_equal("yuu", cat_or_fish.member_names)
|
||||
assert_not_nil(Artist.search(:group_name => "cat_or_fish").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "cat_or_fish").first)
|
||||
assert_not_nil(Artist.search(:any_name_matches => "/cat/").first)
|
||||
assert_not_nil(Artist.search(group_name: "cat_or_fish").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "cat_or_fish").first)
|
||||
assert_not_nil(Artist.search(any_name_matches: "*cat*").first)
|
||||
end
|
||||
|
||||
should "search on url and return matches" do
|
||||
@ -231,7 +231,7 @@ class ArtistTest < ActiveSupport::TestCase
|
||||
|
||||
assert_equal([bkub.id], Artist.search(url_matches: "bkub").map(&:id))
|
||||
assert_equal([bkub.id], Artist.search(url_matches: "*bkub*").map(&:id))
|
||||
assert_equal([bkub.id], Artist.search(url_matches: "/rifyu|bkub/").map(&:id))
|
||||
assert_equal([], Artist.search(url_matches: "*rifyu*").map(&:id))
|
||||
assert_equal([bkub.id], Artist.search(url_matches: "http://bkub.com/test.jpg").map(&:id))
|
||||
end
|
||||
|
||||
|
@ -93,11 +93,9 @@ class ArtistUrlTest < ActiveSupport::TestCase
|
||||
assert_search_equals([@bkub_url], artist: { name: "bkub" })
|
||||
|
||||
assert_search_equals([@bkub_url], url_matches: "*bkub*")
|
||||
assert_search_equals([@bkub_url], url_matches: "/^https?://bkub\.com$/")
|
||||
|
||||
assert_search_equals([@bkub_url], normalized_url_matches: "*bkub*")
|
||||
assert_search_equals([@bkub_url], normalized_url_matches: "/^https?://bkub\.com/$/")
|
||||
assert_search_equals([@bkub_url], normalized_url_matches: "https://bkub.com")
|
||||
assert_search_equals([@bkub_url], normalized_url_matches: "http://bkub.com")
|
||||
|
||||
assert_search_equals([@bkub_url], url: "https://bkub.com")
|
||||
assert_search_equals([@bkub_url], url_eq: "https://bkub.com")
|
||||
|
@ -34,11 +34,11 @@ module PostSets
|
||||
end
|
||||
|
||||
should "know the total number of pages" do
|
||||
assert_equal(3, @set.total_pages)
|
||||
assert_equal(3, @set.posts.total_pages)
|
||||
end
|
||||
|
||||
should "know the current page" do
|
||||
assert_equal(2, @set.current_page)
|
||||
assert_equal(2, @set.posts.current_page)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1988,15 +1988,6 @@ class PostTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
context "Voting:" do
|
||||
# TODO: What the heck is this about?
|
||||
# should "not allow members to vote" do
|
||||
# @user = FactoryBot.create(:user)
|
||||
# @post = FactoryBot.create(:post)
|
||||
# as_user do
|
||||
# assert_raises(PostVote::Error) { VoteManager.vote!(user: @user, post: @post, score: 1) }
|
||||
# end
|
||||
# end
|
||||
|
||||
should "not allow duplicate votes" do
|
||||
user = FactoryBot.create(:privileged_user)
|
||||
post = FactoryBot.create(:post)
|
||||
|
@ -23,7 +23,7 @@ class PostVoteTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "not accept any other scores" do
|
||||
error = assert_raises(PostVote::Error) { VoteManager.vote!(user: @user, post: @post, score: 'xxx') }
|
||||
error = assert_raises(UserVote::Error) { VoteManager.vote!(user: @user, post: @post, score: 'xxx') }
|
||||
assert_equal("Invalid vote", error.message)
|
||||
end
|
||||
|
||||
|
@ -2,11 +2,11 @@ require 'test_helper'
|
||||
|
||||
class UserDeletionTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
Sidekiq::Testing::inline!
|
||||
Sidekiq::Testing.inline!
|
||||
end
|
||||
|
||||
teardown do
|
||||
Sidekiq::Testing::fake!
|
||||
Sidekiq::Testing.fake!
|
||||
end
|
||||
|
||||
context "an invalid user deletion" do
|
||||
@ -43,7 +43,7 @@ class UserDeletionTest < ActiveSupport::TestCase
|
||||
|
||||
context "a valid user deletion" do
|
||||
setup do
|
||||
@user = FactoryBot.create(:user, created_at: 2.weeks.ago)
|
||||
@user = FactoryBot.create(:privileged_user, created_at: 2.weeks.ago)
|
||||
CurrentUser.user = @user
|
||||
CurrentUser.ip_addr = "127.0.0.1"
|
||||
|
||||
@ -69,6 +69,10 @@ class UserDeletionTest < ActiveSupport::TestCase
|
||||
assert_nil(User.authenticate(@user.name, "password"))
|
||||
end
|
||||
|
||||
should "reset the level" do
|
||||
assert_equal(User::Levels::MEMBER, @user.level)
|
||||
end
|
||||
|
||||
should "remove any favorites" do
|
||||
@post.reload
|
||||
assert_equal(0, Favorite.count)
|
||||
|
Loading…
Reference in New Issue
Block a user