diff --git a/Gemfile b/Gemfile index c8f29a72..75816488 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ ruby "~> 2.7" # Server gem 'rails' -gem 'puma', '~> 5.5' +gem 'puma', '~> 5.6' gem 'puma-heroku' # gem 'bootsnap', require: false gem 'sprockets', '~> 3.7.2' @@ -107,7 +107,7 @@ group :production do end group :test, :production do - gem 'pg', '~> 1.2' + gem 'pg', '~> 1.3' gem "mini_racer", "~> 0.6.2" end diff --git a/Gemfile.lock b/Gemfile.lock index d914f35a..f04610fe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,68 +41,71 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.1.4.1) - actionpack (= 6.1.4.1) - activesupport (= 6.1.4.1) + actioncable (6.1.4.6) + actionpack (= 6.1.4.6) + activesupport (= 6.1.4.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4.1) - actionpack (= 6.1.4.1) - activejob (= 6.1.4.1) - activerecord (= 6.1.4.1) - activestorage (= 6.1.4.1) - activesupport (= 6.1.4.1) + actionmailbox (6.1.4.6) + actionpack (= 6.1.4.6) + activejob (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) mail (>= 2.7.1) - actionmailer (6.1.4.1) - actionpack (= 6.1.4.1) - actionview (= 6.1.4.1) - activejob (= 6.1.4.1) - activesupport (= 6.1.4.1) + actionmailer (6.1.4.6) + actionpack (= 6.1.4.6) + actionview (= 6.1.4.6) + activejob (= 6.1.4.6) + activesupport (= 6.1.4.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.4.1) - actionview (= 6.1.4.1) - activesupport (= 6.1.4.1) + actionpack (6.1.4.6) + actionview (= 6.1.4.6) + activesupport (= 6.1.4.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4.1) - actionpack (= 6.1.4.1) - activerecord (= 6.1.4.1) - activestorage (= 6.1.4.1) - activesupport (= 6.1.4.1) + actiontext (6.1.4.6) + actionpack (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) nokogiri (>= 1.8.5) - actionview (6.1.4.1) - activesupport (= 6.1.4.1) + actionview (6.1.4.6) + activesupport (= 6.1.4.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) active_record_union (1.3.0) activerecord (>= 4.0) - active_storage_validations (0.9.5) - rails (>= 5.2.0) - activejob (6.1.4.1) - activesupport (= 6.1.4.1) + active_storage_validations (0.9.7) + activejob (>= 5.2.0) + activemodel (>= 5.2.0) + activestorage (>= 5.2.0) + activesupport (>= 5.2.0) + activejob (6.1.4.6) + activesupport (= 6.1.4.6) globalid (>= 0.3.6) - activemodel (6.1.4.1) - activesupport (= 6.1.4.1) + activemodel (6.1.4.6) + activesupport (= 6.1.4.6) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.4.1) - activemodel (= 6.1.4.1) - activesupport (= 6.1.4.1) - activestorage (6.1.4.1) - actionpack (= 6.1.4.1) - activejob (= 6.1.4.1) - activerecord (= 6.1.4.1) - activesupport (= 6.1.4.1) + activerecord (6.1.4.6) + activemodel (= 6.1.4.6) + activesupport (= 6.1.4.6) + activestorage (6.1.4.6) + actionpack (= 6.1.4.6) + activejob (= 6.1.4.6) + activerecord (= 6.1.4.6) + activesupport (= 6.1.4.6) marcel (~> 1.0.0) mini_mime (>= 1.1.0) - activesupport (6.1.4.1) + activesupport (6.1.4.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -117,7 +120,7 @@ GEM autoprefixer-rails (10.3.3.0) execjs (~> 2) aws-eventstream (1.2.0) - aws-partitions (1.543.0) + aws-partitions (1.558.0) aws-sdk (3.1.0) aws-sdk-resources (~> 3) aws-sdk-accessanalyzer (1.23.0) @@ -315,7 +318,7 @@ GEM aws-sdk-connectparticipant (1.14.0) aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.125.0) + aws-sdk-core (3.127.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -578,8 +581,8 @@ GEM aws-sdk-kinesisvideosignalingchannels (1.13.0) aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kms (1.53.0) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-kms (1.55.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) aws-sdk-lakeformation (1.17.0) aws-sdk-core (~> 3, >= 3.120.0) @@ -1086,8 +1089,8 @@ GEM aws-sdk-route53resolver (1.30.0) aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.110.0) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-s3 (1.113.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sdk-s3control (1.40.0) @@ -1258,7 +1261,7 @@ GEM binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) builder (3.2.4) - bullet (6.1.5) + bullet (7.0.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.9.0.1) @@ -1295,7 +1298,7 @@ GEM db_text_search (0.3.2) activerecord (>= 4.1.15, < 7.0) debug_inspector (1.1.0) - devise (4.8.0) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -1312,7 +1315,7 @@ GEM docile (1.1.5) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - engtagger (0.2.1) + engtagger (0.2.2) erubi (1.10.0) event_emitter (0.2.6) eventmachine (1.2.7) @@ -1324,7 +1327,7 @@ GEM faye-websocket (0.11.1) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.15.4) + ffi (1.15.5) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -1334,7 +1337,7 @@ GEM railties (>= 3.2, < 8.0) friendly_id (5.4.2) activerecord (>= 4.0.0) - globalid (0.5.2) + globalid (1.0.0) activesupport (>= 5.0) haml (5.2.2) temple (>= 0.8.0) @@ -1354,7 +1357,7 @@ GEM http-form_data (2.3.0) http-parser (1.2.3) ffi-compiler (>= 1.0, < 2.0) - i18n (1.8.11) + i18n (1.10.0) concurrent-ruby (~> 1.0) ibm_cloud_sdk_core (1.1.3) concurrent-ruby (~> 1.0) @@ -1367,13 +1370,13 @@ GEM http (~> 4.4.0) ibm_cloud_sdk_core (~> 1.1.3) jwt (~> 2.2.1) - image_processing (1.12.1) + image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) inline_svg (1.7.2) activesupport (>= 3.0) nokogiri (>= 1.6) - jmespath (1.4.0) + jmespath (1.6.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -1401,10 +1404,10 @@ GEM language_filter (0.3.01) libv8-node (16.10.0.0) libv8-node (16.10.0.0-x86_64-linux) - listen (3.7.0) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.13.0) + loofah (2.15.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -1426,7 +1429,7 @@ GEM rake mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.7.1) + mini_portile2 (2.8.0) mini_racer (0.6.2) libv8-node (~> 16.10.0.0) minitest (5.15.0) @@ -1435,12 +1438,12 @@ GEM mustache (1.1.1) nested_form (0.3.2) netrc (0.11.0) - newrelic_rpm (8.3.0) + newrelic_rpm (8.5.0) nio4r (2.5.8) - nokogiri (1.13.1) - mini_portile2 (~> 2.7.0) + nokogiri (1.13.3) + mini_portile2 (~> 2.8.0) racc (~> 1.4) - nokogiri (1.13.1-x86_64-linux) + nokogiri (1.13.3-x86_64-linux) racc (~> 1.4) onebox (2.2.19) addressable (~> 2.8.0) @@ -1458,7 +1461,7 @@ GEM mime-types mimemagic (~> 0.3.0) terrapin (~> 0.6.0) - paranoia (2.5.0) + paranoia (2.5.2) activerecord (>= 5.1, < 7.1) paypal-checkout-sdk (1.0.4) paypalhttp (~> 1.0.1) @@ -1467,12 +1470,12 @@ GEM faraday (~> 0.15) faraday_middleware (~> 0.12) paypalhttp (1.0.1) - pg (1.2.3) + pg (1.3.4) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.5.1) + puma (5.6.2) nio4r (~> 2.0) puma-heroku (2.0.0) puma (>= 5.0, < 6.0) @@ -1480,7 +1483,7 @@ GEM activesupport (>= 3.0.0) racc (1.6.0) rack (2.2.3) - rack-mini-profiler (2.3.3) + rack-mini-profiler (3.0.0) rack (>= 1.2.0) rack-pjax (1.1.0) nokogiri (~> 1.5) @@ -1489,20 +1492,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.4.1) - actioncable (= 6.1.4.1) - actionmailbox (= 6.1.4.1) - actionmailer (= 6.1.4.1) - actionpack (= 6.1.4.1) - actiontext (= 6.1.4.1) - actionview (= 6.1.4.1) - activejob (= 6.1.4.1) - activemodel (= 6.1.4.1) - activerecord (= 6.1.4.1) - activestorage (= 6.1.4.1) - activesupport (= 6.1.4.1) + rails (6.1.4.6) + actioncable (= 6.1.4.6) + actionmailbox (= 6.1.4.6) + actionmailer (= 6.1.4.6) + actionpack (= 6.1.4.6) + actiontext (= 6.1.4.6) + actionview (= 6.1.4.6) + activejob (= 6.1.4.6) + activemodel (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) bundler (>= 1.15.0) - railties (= 6.1.4.1) + railties (= 6.1.4.6) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) @@ -1527,9 +1530,9 @@ GEM sassc-rails (>= 1.3, < 3) rails_gravatar (1.0.4) actionview - railties (6.1.4.1) - actionpack (= 6.1.4.1) - activesupport (= 6.1.4.1) + railties (6.1.4.6) + actionpack (= 6.1.4.6) + activesupport (= 6.1.4.6) method_source rake (>= 0.13) thor (~> 1.0) @@ -1544,7 +1547,7 @@ GEM railties (>= 3.2) tilt redcarpet (3.5.1) - redis (4.5.1) + redis (4.6.0) remotipart (1.4.4) responders (3.0.1) actionpack (>= 5.0) @@ -1557,8 +1560,8 @@ GEM rexml (3.2.5) rinku (2.0.6) rmagick (4.2.4) - ruby-vips (2.0.17) - ffi (~> 1.9) + ruby-vips (2.1.4) + ffi (~> 1.12) rubyzip (2.3.2) sanitize (6.0.0) crass (~> 1.0.2) @@ -1578,7 +1581,7 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) semantic_range (3.0.0) - sidekiq (6.2.2) + sidekiq (6.4.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -1595,15 +1598,15 @@ GEM babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.4.2) - stackprof (0.2.17) + stackprof (0.2.19) statsd-ruby (1.5.0) - stripe (5.42.0) - stripe_event (2.3.1) + stripe (5.45.0) + stripe_event (2.3.2) activesupport (>= 3.1) stripe (>= 2.8, < 6) temple (0.8.2) @@ -1645,7 +1648,7 @@ GEM websocket-extensions (0.1.5) word_count_analyzer (1.0.1) engtagger - zeitwerk (2.5.3) + zeitwerk (2.5.4) PLATFORMS ruby @@ -1691,9 +1694,9 @@ DEPENDENCIES paranoia paypal-checkout-sdk paypal_client - pg (~> 1.2) + pg (~> 1.3) pry - puma (~> 5.5) + puma (~> 5.6) puma-heroku rack-mini-profiler rails diff --git a/app/assets/stylesheets/content.css.scss b/app/assets/stylesheets/content.css.scss index 97363d86..c2b25ce3 100644 --- a/app/assets/stylesheets/content.css.scss +++ b/app/assets/stylesheets/content.css.scss @@ -34,7 +34,8 @@ p.long-form { a { display: block; - padding: 10px 20px; + padding: 10px; + padding-right: 0; } } diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb index 5a834dd1..abf5b9cc 100644 --- a/app/controllers/content_controller.rb +++ b/app/controllers/content_controller.rb @@ -304,6 +304,7 @@ class ContentController < ApplicationController @content = content_type.find_by(id: params[:id]) return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil? @serialized_content = ContentSerializer.new(@content) + return redirect_to(root_path, notice: "You don't have permission to view that content.") unless @content.updatable_by?(current_user || User.new) if user_signed_in? @navbar_actions << { diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb index f484adfa..a4f7b4cc 100644 --- a/app/controllers/documents_controller.rb +++ b/app/controllers/documents_controller.rb @@ -4,8 +4,6 @@ class DocumentsController < ApplicationController # todo Uh, this is a hack. The CSRF token on document editor model to add entities is being rejected... for whatever reason. skip_before_action :verify_authenticity_token, only: [:link_entity] - skip_before_action :cache_most_used_page_information, only: [:update] - before_action :set_document, only: [:show, :analysis, :plaintext, :queue_analysis, :edit, :destroy] before_action :set_sidenav_expansion, except: [:plaintext] before_action :set_navbar_color, except: [:plaintext] diff --git a/app/controllers/export_controller.rb b/app/controllers/export_controller.rb index b9a20063..ac3c2f5f 100644 --- a/app/controllers/export_controller.rb +++ b/app/controllers/export_controller.rb @@ -2,6 +2,9 @@ class ExportController < ApplicationController before_action :authenticate_user! before_action :whitelist_pluralized_model, only: [:csv] + skip_before_action :cache_most_used_page_information, only: [:outline, :notebook_json] + skip_before_action :cache_forums_unread_counts, only: [:outline, :notebook_json] + def index @sidenav_expansion = 'my account' end @@ -11,11 +14,17 @@ class ExportController < ApplicationController end def outline - send_data content_to_outline, filename: "notebook-#{Date.today}.txt" + export = ExportService.text_outline_export(current_user.universes.pluck(:id)) + send_data export, filename: "notebook-#{Date.today}.txt" + end + + def markdown + export = ExportService.text_markdown_export(current_user.universes.pluck(:id)) + send_data export, filename: "notebook-#{Date.today}.md" end def notebook_json - json_dump = current_user.content.except('Document').except('Timeline').map { |category, content| {"#{category}": fill_relations(category.constantize, content)} }.to_json + json_dump = ExportService.json_export(current_user.universes.pluck(:id)) send_data json_dump, filename: "notebook-#{Date.today}.json" end diff --git a/app/jobs/export_job.rb b/app/jobs/export_job.rb new file mode 100644 index 00000000..0333ae1c --- /dev/null +++ b/app/jobs/export_job.rb @@ -0,0 +1,14 @@ +class ExportJob < ApplicationJob + queue_as :export + + def perform(*args) + universe_ids = args.shift + format = args.shift + + # design: left side 1/2 - list universes with toggle/checkboxes to include + # right side 1/2 - radios for format, checkboxes for options after + # (include hidden? include IDs? include blank fields?) + + + end +end diff --git a/app/jobs/pay_pal_prepay_processing_job.rb b/app/jobs/pay_pal_prepay_processing_job.rb index 3ca107af..5b025b37 100644 --- a/app/jobs/pay_pal_prepay_processing_job.rb +++ b/app/jobs/pay_pal_prepay_processing_job.rb @@ -27,6 +27,10 @@ class PayPalPrepayProcessingJob < ApplicationJob unless invoice.status == 'COMPLETED' invoice.update(status: 'COMPLETED') invoice.generate_promo_code! + + # Add the extra Premium space + SubscriptionService.add_any_referral_bonuses(invoice.user, 'premium') + PremiumDowngradeJob.set(wait: (invoice.months).months).perform_later(invoice.user_id) end else diff --git a/app/jobs/premium_downgrade_job.rb b/app/jobs/premium_downgrade_job.rb new file mode 100644 index 00000000..c4ad07e2 --- /dev/null +++ b/app/jobs/premium_downgrade_job.rb @@ -0,0 +1,15 @@ +class PremiumDowngradeJob < ApplicationJob + queue_as :low_priority + + def perform(*args) + user_id = args.shift + + user = User.find_by(id: user_id) + raise "No user to downgrade; id=#{user_id}" if user.nil? + + # Remove any bonus bandwidth granted by the plan + premium_bandwidth_bonus = BillingPlan.find(4).bonus_bandwidth_kb + user.update!(upload_bandwidth_kb: user.upload_bandwidth_kb - premium_bandwidth_bonus) + end + +end \ No newline at end of file diff --git a/app/models/concerns/has_page_references.rb b/app/models/concerns/has_page_references.rb index 8be704e4..cfc70595 100644 --- a/app/models/concerns/has_page_references.rb +++ b/app/models/concerns/has_page_references.rb @@ -6,8 +6,9 @@ module HasPageReferences included do # Pages that reference this one has_many :incoming_page_references, - as: :referenced_page, - class_name: PageReference.name + as: :referenced_page, + class_name: PageReference.name, + dependent: :destroy Rails.application.config.content_type_names[:all].each do |page_type| has_many "referencing_#{page_type.downcase}_pages".to_sym, through: :incoming_page_references, @@ -26,7 +27,8 @@ module HasPageReferences # Pages referenced by this one has_many :outgoing_page_references, as: :referencing_page, - class_name: PageReference.name + class_name: PageReference.name, + dependent: :destroy Rails.application.config.content_type_names[:all].each do |page_type| has_many "referenced_#{page_type.downcase}_pages".to_sym, through: :outgoing_page_references, diff --git a/app/models/page_data/attribute_field.rb b/app/models/page_data/attribute_field.rb index 5b4c34dc..4724503a 100644 --- a/app/models/page_data/attribute_field.rb +++ b/app/models/page_data/attribute_field.rb @@ -7,6 +7,8 @@ class AttributeField < ApplicationRecord belongs_to :attribute_category has_many :attribute_values, class_name: 'Attribute', dependent: :destroy + has_many :page_references, dependent: :destroy + validates_presence_of :user_id acts_as_list scope: [:user_id, :attribute_category_id] diff --git a/app/models/page_types/creature.rb b/app/models/page_types/creature.rb index 143e9bac..363146f5 100644 --- a/app/models/page_types/creature.rb +++ b/app/models/page_types/creature.rb @@ -20,7 +20,7 @@ class Creature < ApplicationRecord include Serendipitous::Concern include Authority::Abilities - self.authorizer_name = 'CoreContentAuthorizer' + self.authorizer_name = 'ExtendedContentAuthorizer' # Locations relates :habitats, with: :wildlifeships diff --git a/app/models/page_types/language.rb b/app/models/page_types/language.rb index 6217e5e1..14c2d8ff 100644 --- a/app/models/page_types/language.rb +++ b/app/models/page_types/language.rb @@ -16,8 +16,7 @@ class Language < ApplicationRecord self.authorizer_name = 'ExtendedContentAuthorizer' def description - num_speakers = Lingualism.where(spoken_language_id: id).count - "Language spoken by #{ActionController::Base.helpers.pluralize num_speakers, 'character'}" + overview_field_value('Description') end def self.color diff --git a/app/services/export_service.rb b/app/services/export_service.rb new file mode 100644 index 00000000..f9b16ebd --- /dev/null +++ b/app/services/export_service.rb @@ -0,0 +1,323 @@ +class ExportService < Service + INCLUDE_EMPTY_FIELD_LABELS = false + + def self.text_outline_export(universe_ids) + page_types = Rails.application.config.content_types[:all] + temporary_user_id_reference = Universe.find_by(id: universe_ids).user_id + + export_text = StringIO.new + + categories = fetch_categories(user_id: temporary_user_id_reference) + fields = fetch_fields(category_ids: categories.map(&:id)) + values = fetch_values(field_ids: fields.map(&:id)) + + page_types.each do |page_type| + # Get all pages in the given universe(s) + pages = (page_type.name == 'Universe') ? page_type.where(id: universe_ids) + : page_type.where(universe_id: universe_ids) + if pages.any? + export_text << "# #{page_type.name.pluralize}\n" + + categories_for_this_page_type = categories.select { |c| c.entity_type == page_type.name.downcase } + fields_for_this_page_type = fields.select { |f| categories_for_this_page_type.map(&:id).include? f.attribute_category_id } + + pages.each do |page| + export_text << "* Name: #{page.name}\n" + export_text << " ID: #{page.id}\n" + + values_for_this_page = values.select { |v| v.entity_type == page_type.name && v.entity_id == page.id } + + categories_for_this_page_type.each do |category| + # Delay printing our category header until we've found a value to print, so we don't print + # empty headers and blank sections. + printed_category_header = false + + fields_for_this_category = fields_for_this_page_type.select { |f| f.attribute_category_id == category.id } + values_for_these_fields = values_for_this_page.select { |v| fields_for_this_category.map(&:id).include? v.attribute_field_id } + + fields_for_this_category.each do |field| + value_for_this_field = values_for_these_fields.detect do |value| + value.attribute_field_id == field.id && + value.entity_type == page_type.name && + value.entity_id == page.id + end + + # If we have a field to print but haven't printed the header yet, print it + if value_for_this_field.try(:value).try(:present?) && !printed_category_header + export_text << " #{category.label}\n" + printed_category_header = true + end + + case field.field_type + when 'text_area', 'textarea' + # For answers to text fields, we can just output their value directly + if value_for_this_field.present? || INCLUDE_EMPTY_FIELD_LABELS + export_text << " #{field.label}: #{value_for_this_field.try :value}\n" + end + + when 'link' + # For link fields, we have link codes ([[Character-1]]) and need to translate + # them into the linked page's name in order for them to actually be useful + if value_for_this_field.present? + json_list = JSON.parse(value_for_this_field.value) + formatted_names = json_list.map do |link_code| + query_link_code_with_cache(*link_code.split('-')).name + end + + export_text << " #{field.label}: #{formatted_names.to_sentence}\n" + else + if INCLUDE_EMPTY_FIELD_LABELS + export_text << " #{field.label}:\n" + end + end + end + + end + end + + export_text << "\n" # spacer between pages + end + + export_text << "\n" # spacer between sections + end + end + + return export_text.string + end + + def self.text_markdown_export(universe_ids) + page_types = Rails.application.config.content_types[:all] + temporary_user_id_reference = Universe.find_by(id: universe_ids).user_id + + export_text = StringIO.new + + categories = fetch_categories(user_id: temporary_user_id_reference) + fields = fetch_fields(category_ids: categories.map(&:id)) + values = fetch_values(field_ids: fields.map(&:id)) + + page_types.each do |page_type| + # Get all pages in the given universe(s) + pages = (page_type.name == 'Universe') ? page_type.where(id: universe_ids) + : page_type.where(universe_id: universe_ids) + if pages.any? + export_text << "# #{page_type.name.pluralize}\n" + + categories_for_this_page_type = categories.select { |c| c.entity_type == page_type.name.downcase } + fields_for_this_page_type = fields.select { |f| categories_for_this_page_type.map(&:id).include? f.attribute_category_id } + + pages.each do |page| + export_text << "## #{page.name}\n" + export_text << "ID: #{page.id}\n" + + values_for_this_page = values.select { |v| v.entity_type == page_type.name && v.entity_id == page.id } + + categories_for_this_page_type.each do |category| + # Delay printing our category header until we've found a value to print, so we don't print + # empty headers and blank sections. + printed_category_header = false + + fields_for_this_category = fields_for_this_page_type.select { |f| f.attribute_category_id == category.id } + values_for_these_fields = values_for_this_page.select { |v| fields_for_this_category.map(&:id).include? v.attribute_field_id } + + fields_for_this_category.each do |field| + value_for_this_field = values_for_these_fields.detect do |value| + value.attribute_field_id == field.id && + value.entity_type == page_type.name && + value.entity_id == page.id + end + + # If we have a field to print but haven't printed the header yet, print it + if value_for_this_field.try(:value).try(:present?) && !printed_category_header + export_text << "### #{category.label}\n" + printed_category_header = true + end + + case field.field_type + when 'text_area', 'textarea' + # For answers to text fields, we can just output their value directly + if value_for_this_field.try(:value).present? || INCLUDE_EMPTY_FIELD_LABELS + export_text << "#### #{field.label}\n#{value_for_this_field.try :value}\n" + end + + when 'link' + # For link fields, we have link codes ([[Character-1]]) and need to translate + # them into the linked page's name in order for them to actually be useful + if value_for_this_field.present? + json_list = JSON.parse(value_for_this_field.value) + formatted_names = json_list.map do |link_code| + link_type, link_id = link_code.split('-') + page_ref = query_link_code_with_cache(link_type, link_id) + + "[#{page_ref.name}](https://www.notebook.ai/plan/#{link_type.downcase.pluralize}/#{link_id})" + end + + export_text << "#### #{field.label}\n#{formatted_names.to_sentence}\n" + else + if INCLUDE_EMPTY_FIELD_LABELS + export_text << "#### #{field.label}\n" + end + end + end + + end + end + + export_text << "\n" # spacer between pages + end + + export_text << "\n" # spacer between sections + end + end + + return export_text.string + end + + def self.csv_export(universe_ids, separator=',') + # todo: figure out zip + end + + def self.json_export(universe_ids) + page_types = Rails.application.config.content_types[:all] + temporary_user_id_reference = Universe.find_by(id: universe_ids).user_id + + export_object = {} + + categories = fetch_categories(user_id: temporary_user_id_reference) + fields = fetch_fields(category_ids: categories.map(&:id)) + values = fetch_values(field_ids: fields.map(&:id)) + + page_types.each do |page_type| + # Get all pages in the given universe(s) + pages = (page_type.name == 'Universe') ? page_type.where(id: universe_ids) + : page_type.where(universe_id: universe_ids) + if pages.any? + export_object[page_type.name] = [] + + categories_for_this_page_type = categories.select { |c| c.entity_type == page_type.name.downcase } + fields_for_this_page_type = fields.select { |f| categories_for_this_page_type.map(&:id).include? f.attribute_category_id } + + page_object = {} + + pages.each do |page| + page_object['name'] = page.name + page_object['id'] = page.id + + values_for_this_page = values.select { |v| v.entity_type == page_type.name && v.entity_id == page.id } + + categories_for_this_page_type.each do |category| + page_object[category.label] ||= {} + + fields_for_this_category = fields_for_this_page_type.select { |f| f.attribute_category_id == category.id } + values_for_these_fields = values_for_this_page.select { |v| fields_for_this_category.map(&:id).include? v.attribute_field_id } + + fields_for_this_category.each do |field| + value_for_this_field = values_for_these_fields.detect do |value| + value.attribute_field_id == field.id && + value.entity_type == page_type.name && + value.entity_id == page.id + end + + case field.field_type + when 'text_area', 'textarea' + # For answers to text fields, we can just output their value directly + if value_for_this_field.try(:value).present? || INCLUDE_EMPTY_FIELD_LABELS + page_object[category.label][field.label] = value_for_this_field.try :value + end + + when 'link' + # For link fields, we have link codes ([[Character-1]]) and need to translate + # them into the linked page's name in order for them to actually be useful + if value_for_this_field.present? + json_list = JSON.parse(value_for_this_field.value) + formatted_name_objects = json_list.map do |link_code| + link_type, link_id = link_code.split('-') + page_ref = query_link_code_with_cache(link_type, link_id) + + name_object = { + name: page_ref.name, + id: page_ref.id, + } + end + + page_object[category.label][field.label] = formatted_name_objects + else + if INCLUDE_EMPTY_FIELD_LABELS + page_object[category.label][field.label] = nil + end + end + end + + end + end + end + + # Append the JSON page object we just built to the list of pages of that kind + export_object[page_type.name].push page_object + end + end + + return export_object.to_json + end + + def self.xml_export(universe_ids) + + end + + def self.yaml_export(universe_ids) + + end + + def self.html_export(universe_ids) + + end + + def self.scrivener_export(universe_ids) + + end + + private + + def self.fetch_categories(user_id:) + AttributeCategory.where(user_id: user_id) + .order('position ASC') + .select(:id, :label, :entity_type) + end + + def self.fetch_fields(category_ids:) + AttributeField.where(attribute_category_id: category_ids) + .order('position ASC') + .select(:id, :label, :attribute_category_id, :field_type) + end + + def self.fetch_values(field_ids:) + Attribute.where(attribute_field_id: field_ids) + .select(:attribute_field_id, :value, :entity_type, :entity_id) + end + + def self.fetch_universes(universe_ids) + + end + + def self.query_link_code_with_cache(content_type_name, content_id) + cache_key = "#{content_type_name}-#{content_id}" + + # Pull from the cache to avoid a query if we can + @content_cache ||= {} + if @content_cache.key?(cache_key) + return @content_cache[cache_key] + end + + # TODO: we should probably whitelist content_type from valid page types here + + # If there's no cache, we unfortunately need to do a query to resolve the link code + content = class_from_name(content_type_name).find(content_id) + @content_cache[cache_key] = content + + return content + end + + def self.class_from_name(content_type_name) + # This lookup is ~3x faster than .constantize + Rails.application.config.content_types_by_name[content_type_name] + end +end \ No newline at end of file diff --git a/app/services/forum_replacement_service.rb b/app/services/forum_replacement_service.rb index 7d0576f9..249b6401 100644 --- a/app/services/forum_replacement_service.rb +++ b/app/services/forum_replacement_service.rb @@ -2,8 +2,40 @@ class ForumReplacementService < Service # Cool replacements we could eventually do not for pranking # [roll N] = random number between 1 and N # [roll N, M] = random number between N and M + # [@reader] = username of the reader - WORD_REPLACEMENTS = { + # NOTE: could do this shader style on word replacements if we want a ui :) [ReplacementList(color, replacements, &active?)] + + # blackout replacements + FORBIDDEN_WORD_REPLACEMENTS = { + + } + + SPAM_WORD_REPLACEMENTS = { + 'cash app' => 'pls ban me andrew', + 'cashapp' => 'hey mr andrew come over here' + } + + # perspective changes, always surrounded by {} (e.g. {@reader} ) + PERSPECTIVE_REPLACEMENTS = { + '@reader' => Proc.new { |trigger, user| (user.nil? ? 'anonymous reader' : user.try(:display_name)) || 'anonymous reader' } + # 'their' => Proc.new { |_, _| ["they're", "their", "there"].sample } + # 'there' => Proc.new { |_, _| ["they're", "their", "there"].sample } + # "they're" => Proc.new { |_, _| ["they're", "their", "there"].sample } + } + + # turn on and off at will + CASUAL_CHAOS_WORD_REPLACEMENTS = { + 'andrew' => 'andrew (Our Supreme Lord and Overseer)', + 'amy' => 'amy (the most wonderful woman in the world)', + } + + REFERENCE_REPLACEMENTS = { + # [[Character-4]] + } + + # gremlin replacements + GREMLINS_WORD_REPLACEMENTS = { '<3' => "&heart;", '$5' => '[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]', '0/10' => '10/10', @@ -22,9 +54,8 @@ class ForumReplacementService < Service 'alchohol' => 'giggle juice', 'alien' => 'extraterrestrial bopper', 'aliens' => 'extraterrestrial boppers', - 'amy' => 'the most wonderful woman in the world', + 'alpha' => 'beta', 'an hour' => 'exactly 3600 seconds', - 'andrew' => 'andrew (Our Supreme Lord and Overseer)', 'announcement' => 'shouty spouty', 'announcements' => 'shouty spouties', "April Fool's Day" => 'the best holiday of the year', @@ -36,13 +67,13 @@ class ForumReplacementService < Service 'askew' => 'cattywampus', 'attorney' => 'lawyerboi-at-law', 'attornies' => 'lawyerbois-at-law', + 'automatically' => 'automagically', 'autumn' => 'rainseason (bestseason)', 'aviators' => 'cool kid shades', 'avocado' => 'greenseed toastmash', 'aware' => 'cognizant', 'awesome' => "bee's knees", 'B&B' => 'Bungeons & Bragons', - 'bad' => 'bodge', 'ball' => 'blimpy bounce bounce', 'balls' => 'blimpy bounce bounces', 'balloon' => 'elastic breath trap', @@ -53,6 +84,7 @@ class ForumReplacementService < Service 'bears' => '(ᵔᴥᵔ)(ᵔᴥᵔ)', 'bed' => 'nighttime dreamvessel', 'beds' => 'nighttime dreamvessels', + 'beta' => 'alpha', 'bird' => 'government spy drone', 'birds' => 'government spy drones', 'blood' => 'human syrup', @@ -152,14 +184,13 @@ class ForumReplacementService < Service 'grass' => 'groundfuzzy', 'gravy' => 'meat water', 'greenhouse' => 'clearhouse', - 'gremlin' => 'extremely smart alien being that is here to help', - 'gremlins' => 'extremely smart alien beings that are here to help', 'gun' => 'rooty tooty point-n-shooty', 'guns' => 'rooty tooty point-n-shooties', 'hamburger' => 'beef wellington ensemble with lettuce', 'hamburgers' => 'beef wellington ensembles with lettuce', 'hat' => 'fuzzy head chimney', 'hate' => 'absolutely love', + 'h a t e' => 'L O V E', 'heart' => 'corazón', 'hearts' => 'corazones', 'hell' => 'heck', @@ -179,8 +210,6 @@ class ForumReplacementService < Service 'keyboards' => 'hoighty toighty tippy typers', 'kidnap' => 'surprise adoption', 'kitchen' => 'fridge-and-oven combination room', - 'knee' => 'leg hinge', - 'knees' => 'leg hinges', 'knife' => 'stabby stick', 'knives' => 'stabby sticks', 'la croix' => 'water that has been in the vicinity of a fruit at one point in the past few years', @@ -188,7 +217,7 @@ class ForumReplacementService < Service 'laptop' => 'electrotablet with keys', 'lechuga' => 'lettuce', 'lettuce' => 'lechuga', - 'life' => 'like a box of chocolates', + 'library' => 'libary', 'lightbulb' => 'ceiling-bright', 'lightbulbs' => 'ceiling-brights', 'lizard people' => 'definitely normal human beings', @@ -243,7 +272,7 @@ class ForumReplacementService < Service 'prison' => 'hoosegow locker', 'raccoon' => 'trash burgler', 'radio' => 'magic musicbox', - 'rain' => 'cloudy waterdrops', + 'rain' => 'cloudy juice', 'recursion' => 'recursion', 'replaced' => 'improved', 'reverse' => 'esrever', @@ -281,6 +310,7 @@ class ForumReplacementService < Service 'Stardew Valley' => 'Farming Simulator 2016', 'sticky' => 'icky wicky', 'stolen' => 'nick wicketed', + 'stupid' => 'stoopid', 'success' => 'Yahtzee!', 'summer' => 'sunseason', 'sup' => 'soup', @@ -338,55 +368,90 @@ class ForumReplacementService < Service 'writing' => 'scribble scrabbling', 'writer' => 'scribble scrabbler', 'writers' => 'scribble scrabblers', + 'word' => 'wod', + 'words' => 'wods', + 'wordsmith' => 'wodsmith', 'yuge' => 'yuge', 'zalgo' => 'H̶̛̼̼̪̝̞͓̞͕͇̯͚͎͚̘̳͕̱̤̠̗͔͇̙̣̰͓̖̰̯̀̓̐̑̇͊͂̀͋̒̐̓͒̒͊͊̕͜͝ͅE̴̡̧̨̨̲̥̯͎̭̻̩̞̘̞̪̞̗̭͖̻͙͕͎̮͕̺͕̲̘̻̣͚̳̥͍̙͈͚͍͉̗͙̱͖͚̾̂̇͛̉͋͊̾͛̆̀́͑͛̅̋͊̕͘͜͜͜͝ͅͅͅͅ ̸̡̡̨̡̨̛̞͎̹̩̬̗̗̞̬̰̮̙̪̖͈̣̹͔̺̫̰̓̔̉̋̈̈́͐́̿̈̀͊̿̈̉̅̃̊̽͗̈̿̈́̓̈́̎͌̄̀̆̌̎͗̋͒̋̿̋̊̈́͆̋̾̈̏̈́̋̿̕̕̚͝͝͠͠ͅͅͅC̵̛̘̳͙̪̭͖̲̞̯̰̜͇̈̾̈́͋̌̉̽̽͑̎͌̾̈́͌̑͊̊̔̀͆̌̀̇̓͊̀̂̇̿̃͑́̈́̆͂̈́̾̓́̂̂̓̂̍̍͛͆͌͌̽̎̍̀̒̆̀͗͋͘͘͘͝͠͝͝͠͝͝Ǫ̸͕̻̞̝̜͚̗̮̼͎̤͔̤̱͔̫͂̄̉̋̈͊͐͂̇̀̌̎́͑̐̀̈́͋̓̾̅͒̒̄͑̒̆̑̾͜͝͝͝͝M̷̧̧̡̨̛̛̩̭̞͍̼̝̗͕̖͇̣̣̩͆̿̑͒́̉̅̓̌̆̈́͐͒̾̐̂̿̓̚͘̚͜E̵̡̨̢̧̢̢̡̢̨̛̠̱̻̺̦͚̹͓̬͔̪̟̼̥̯̠̘͚̫̯͍̺͔̫̟͇̱̦̟̪͚͉̣̳͓͍̬̙̲͔̘͙͔̤̰̜͍̠̩͉͐̂̊̏̐̿̊̋͑̿̇̊̈́͗̎̋́́̉̓̂̐͑̇̐̐͋́̒̈́͛͑͒̂͒̂̔̀̄̈́̓͂͆̈́͒̌͆̓͗̋͐̔̑͐̕͘ͅͅͅŞ̴̧̧̡̢̧̡̢͕̝͚̝̖͚̣̞̫̻̯͔̳̗̝̰̗̰̰̥̭͕̜̜̫͍̪̳̘̣̺̠͉̗̟͕̹͇̬̘̘̪͆͗̎̕', - - ## Crazy mode - # 'depression' => 'megasadness', - # 'ice cream' => 'eyes cream', - # 'strawberry' => 'plastic tubefruit', - # 'soul' => 'inner ghost', - # 'dance' => 'woopwoop', - # 'curling iron' => 'medieval torture device', - # 'gay' => 'rainbow', - # 'insurance' => 'the biggest scam known to man', - # 'lemonade' => 'sour drank', - # 'salamander' => 'baby dragon', - # 'scrolling' => 'vertically surfing through a screen', - # 'subtext' => 'subtweeting but IRL', - # 'how' => 'how now brown cow', - # 'midnight' => 'dayover', - # 'kill' => 'deathsnuggle', - # 'babe' => 'bae', - # 'video' => 'series of images played in rapid succession to give the illusion of movement on a static screen', - # 'online' => 'on the interwebs', - # 'internet' => 'series of electrotubes', - # 'highlighted' => 'becoming better', - # 'backfired' => 'went incredibly well', - # 'football' => 'soccer', - # 'baseball' => 'thrown soccer', - # 'basketball' => 'dribbling soccer', - # 'volleyball' => 'beach soccer', - # 'rule' => 'law you must obey', - # 'rules' => 'laws you must obey', - # 'why is this happening' => 'I think this is great', } - def self.replace(text) - return text + OVERLOAD_WORDS_REPLACEMENTS = { + 'bad' => 'bodge', + 'knee' => 'leg hinge', + 'knees' => 'leg hinges', + 'depression' => 'megasadness', + 'ice cream' => 'eyes cream', + 'life' => 'like a box of chocolates', + 'strawberry' => 'plastic tubefruit', + 'gremlin' => 'extremely smart alien being that is here to help', + 'gremlins' => 'extremely smart alien beings that are here to help', + 'soul' => 'inner ghost', + 'dance' => 'woopwoop', + 'curling iron' => 'medieval torture device', + 'insurance' => 'the biggest scam known to man', + 'lemonade' => 'sour drank', + 'salamander' => 'baby dragon', + 'scrolling' => 'vertically surfing through a screen', + 'subtext' => 'subtweeting but IRL', + 'how' => 'how now brown cow', + 'midnight' => 'dayover', + 'kill' => 'deathsnuggle', + 'babe' => 'bae', + 'video' => 'series of images played in rapid succession to give the illusion of movement on a static screen', + 'online' => 'on the interwebs', + 'internet' => 'series of electrotubes', + 'highlighted' => 'becoming better', + 'backfired' => 'went incredibly well', + 'football' => 'soccer', + 'baseball' => 'throwing soccer', + 'basketball' => 'dribbling soccer', + 'volleyball' => 'beach air soccer', + 'rule' => 'law you must obey', + 'rules' => 'laws you must obey', + 'why is this happening' => 'I think this is great' + } - # TODO: page tag replacements? + def self.replace_for(text, user) + gremlins_phase = 0 replaced_text = text.dup - WORD_REPLACEMENTS.each do |trigger, replacement| - replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger)) + SPAM_WORD_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'red')) end - + + PERSPECTIVE_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/{#{trigger.downcase}}/i, wrapped(replacement.call(trigger, user), trigger, 'blue')) + end + + if false # not implemented: [[Character-123]] or https://www.notebook.ai/plan/characters/553 etc + REFERENCE_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'notebook-blue')) + end + end + + if gremlins_phase >= 1 + CASUAL_CHAOS_WORD_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'green')) + end + end + + if gremlins_phase >= 2 + GREMLINS_WORD_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'green')) + end + end + + if gremlins_phase >= 3 + OVERLOAD_WORDS_REPLACEMENTS.each do |trigger, replacement| + replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'green')) + end + end + replaced_text.html_safe end - def self.wrapped(text, tooltip) - "#{text}" + def self.wrapped(text, tooltip, color='blue') + "#{text}" end end \ No newline at end of file diff --git a/app/services/subscription_service.rb b/app/services/subscription_service.rb index 23f1336a..12b036bb 100644 --- a/app/services/subscription_service.rb +++ b/app/services/subscription_service.rb @@ -127,4 +127,30 @@ class SubscriptionService < Service ].join("\n") ) end + + def self.recalculate_bandwidth_for(user) + base_bandwidth = User.new.upload_bandwidth_kb # 50_000 + premium_bonus = BillingPlan.find(4).bonus_bandwidth_kb # 9_950_000 + premium_total = base_bandwidth + premium_bonus # 10_000_000 + referral_bonus = 100_000 # per referral + + max_bandwidth = case user.selected_billing_plan_id + when nil, 1 + base_bandwidth + when 2 # free-for-lifers + 250_000 + when 4, 5, 6 # premium + premium_total + else + raise "User with funky billing plan id: U=#{user.id} BP=#{user.selected_billing_plan_id}" + end + + referral_bonus = user.referrals.count * referral_bonus + used_bandwidth = user.image_uploads.sum(:src_file_size) / 1000 + + remaining_bandwidth = max_bandwidth + referral_bonus - used_bandwidth + + return remaining_bandwidth + end + end diff --git a/app/services/technical_debt_service.rb b/app/services/technical_debt_service.rb deleted file mode 100644 index e69de29b..00000000 diff --git a/app/views/content/display/sidebar/_categories.html.erb b/app/views/content/display/sidebar/_categories.html.erb index ed78552e..25e4c3a6 100644 --- a/app/views/content/display/sidebar/_categories.html.erb +++ b/app/views/content/display/sidebar/_categories.html.erb @@ -45,11 +45,11 @@ <%= category[:label].capitalize.presence || '(blank)' %> <% if editing && category[:percent_complete].present? %> <% if category[:percent_complete] == 100 %> - + check <% else %> - + <%= category[:percent_complete] %>% <% end %> diff --git a/app/views/layouts/forum.html.erb b/app/views/layouts/forum.html.erb index 2901e560..9a1b075e 100644 --- a/app/views/layouts/forum.html.erb +++ b/app/views/layouts/forum.html.erb @@ -35,7 +35,6 @@ <%= render 'layouts/ganalytics' %> - <%= react_component("Footer") unless defined?(@show_footer) && !@show_footer %> diff --git a/app/views/thredded/posts/_post.html.erb b/app/views/thredded/posts/_post.html.erb index fda41c46..7d3e50b6 100644 --- a/app/views/thredded/posts/_post.html.erb +++ b/app/views/thredded/posts/_post.html.erb @@ -19,7 +19,7 @@ <%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post #{muted_post ? muted_post_classes : 'card'}" do %> <%= render 'thredded/posts_common/actions', post: post, actions: local_assigns[:actions] %> <%= render 'thredded/posts_common/header', post: post %> - <%= ForumReplacementService.replace(content) || render('thredded/posts/content', post: post) %> + <%= ForumReplacementService.replace_for(content, current_user) || render('thredded/posts/content', post: post) %> <% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %>

<%= t 'thredded.posts.pending_moderation_notice' %>

<% elsif post.blocked? && post.can_moderate? %> diff --git a/app/views/thredded/private_posts/_private_post.html.erb b/app/views/thredded/private_posts/_private_post.html.erb index a224d055..6dce3640 100644 --- a/app/views/thredded/private_posts/_private_post.html.erb +++ b/app/views/thredded/private_posts/_private_post.html.erb @@ -13,6 +13,7 @@ <%= content_tag :article, id: dom_id(private_post), class: "thredded--post thredded--#{private_post.read_state}--post" do %> <%= render 'thredded/posts_common/actions', post: private_post, actions: local_assigns[:actions] %> <%= render 'thredded/posts_common/header', post: private_post %> - <%= content || render('thredded/private_posts/content', post: private_post) %> + <%= ForumReplacementService.replace_for(content, current_user) || render('thredded/private_posts/content', post: private_post) %> + <%# content || render('thredded/private_posts/content', post: private_post) %> <% end %> <% end %> \ No newline at end of file diff --git a/config/attributes/language.yml b/config/attributes/language.yml index 0db82303..f4b9d605 100644 --- a/config/attributes/language.yml +++ b/config/attributes/language.yml @@ -7,6 +7,8 @@ :field_type: name - :name: other_names :label: Other names + - :name: description + :label: Description - :name: universe_id :label: Universe :field_type: universe diff --git a/config/initializers/promos.rb b/config/initializers/promos.rb index 560d1547..b1b5cb9b 100644 --- a/config/initializers/promos.rb +++ b/config/initializers/promos.rb @@ -14,12 +14,3 @@ if Date.current >= 'March 1, 2020'.to_date Rails.application.config.content_types[:premium] -= [Lore] end end - -# Lore free during the month of October -# Need to change Creature.rb authorizer at the end -if Date.current >= 'October 1, 2021'.to_date - if Date.current < 'November 1, 2021'.to_date - Rails.application.config.content_types[:free] << Creature - Rails.application.config.content_types[:premium] -= [Creature] - end -end diff --git a/config/routes.rb b/config/routes.rb index 232b907b..3da255e5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -156,6 +156,7 @@ Rails.application.routes.draw do get '/', to: 'export#index', as: :notebook_export get '/outline', to: 'export#outline', as: :notebook_outline + get '/markdown', to: 'export#markdown', as: :notebook_markdown get '/notebook.json', to: 'export#notebook_json', as: :notebook_json get '/notebook.xml', to: 'export#notebook_xml', as: :notebook_xml get '/notebook.yml', to: 'export#notebook_yml', as: :notebook_yml diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 536aebc4..7b5f8a6e 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -6,4 +6,5 @@ - default - cache - notifications + - export - low_priority diff --git a/lib/tasks/data_integrity.rake b/lib/tasks/data_integrity.rake index 0f738259..35fc3a53 100644 --- a/lib/tasks/data_integrity.rake +++ b/lib/tasks/data_integrity.rake @@ -111,5 +111,47 @@ namespace :data_integrity do end end + + desc "Remove orphan page references" + task remove_orphan_page_references: :environment do + PageReference.find_each do |reference| + if reference.referencing_page.nil? + puts "Deleting reference #{reference.id}" + reference.destroy + next + end + + if reference.referenced_page.nil? + puts "Deleting reference #{reference.id}" + reference.destroy + next + end + end + end + + desc "Ensure all users have the correct upload bandwidth amounts" + task correct_bandwidths: :environment do + puts "Disabling SQL logging" + old_logger = ActiveRecord::Base.logger + ActiveRecord::Base.logger = nil + + # For the sake of minimizing db updates while blazing through all users, + # we ignore a small amount (1kb) of difference between saved bandwidth + # and calculated bandwidth. Users should never be more than 1kb off though. + byte_lenience = 1000 + + User.find_each do |user| + correct_bandwidth = SubscriptionService.recalculate_bandwidth_for(user) + + difference = user.upload_bandwidth_kb - correct_bandwidth + if difference.abs > byte_lenience + # puts "Correcting user #{user.id} bandwidth: #{user.upload_bandwidth_kb} --> #{correct_bandwidth}" + user.update(upload_bandwidth_kb: correct_bandwidth) + end + end + + puts "Re-enabling SQL logging" + ActiveRecord::Base.logger = old_logger + end end diff --git a/test/controllers/page_tags_controller_test.rb b/test/controllers/page_tags_controller_test.rb index 2f89a08a..a3f2da9f 100644 --- a/test/controllers/page_tags_controller_test.rb +++ b/test/controllers/page_tags_controller_test.rb @@ -1,9 +1,5 @@ require 'test_helper' class PageTagsControllerTest < ActionDispatch::IntegrationTest - test "should get remove" do - get page_tags_remove_url - assert_response :success - end end diff --git a/yarn.lock b/yarn.lock index 345851e0..06fb3630 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3312,9 +3312,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.3.6" follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" - integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== for-in@^1.0.2: version "1.0.2" @@ -7365,9 +7365,9 @@ urix@^0.1.0: integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-parse@^1.4.3, url-parse@^1.5.1: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0"