diff --git a/Gemfile b/Gemfile index ffa72a5ee..adbfc1b98 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,7 @@ gem 'will_paginate-bootstrap', '~> 1.0' # AWS for S3 (image storage) and SES (emails). gem 'aws-sdk-s3', '~> 1.61', require: false +gem 'aws-sdk-sns', '~> 1.72' gem 'aws-ses-v4', require: 'aws/ses' # Task scheduler. diff --git a/Gemfile.lock b/Gemfile.lock index d7399a5c6..255450675 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,16 +66,16 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) awesome_print (1.9.2) - aws-eventstream (1.2.0) - aws-partitions (1.628.0) - aws-sdk-core (3.145.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-eventstream (1.3.0) + aws-partitions (1.908.0) + aws-sdk-core (3.191.6) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) @@ -84,12 +84,15 @@ GEM aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) + aws-sdk-sns (1.72.0) + aws-sdk-core (~> 3, >= 3.191.0) + aws-sigv4 (~> 1.1) aws-ses-v4 (0.8.1) builder mail (> 2.2.5) mime-types xml-simple - aws-sigv4 (1.5.1) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.18) bindex (0.8.1) @@ -126,7 +129,7 @@ GEM thor (>= 0.19.4, < 2.0) tins (~> 1.6) crass (1.0.6) - css_parser (1.11.0) + css_parser (1.16.0) addressable date (3.3.4) devise (4.8.1) @@ -209,7 +212,7 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.7.0) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) omniauth (2.1.0) hashie (>= 3.4.6) @@ -227,7 +230,7 @@ GEM premailer-rails (1.11.1) actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) - public_suffix (5.0.0) + public_suffix (5.0.4) puma (5.6.8) nio4r (~> 2.0) racc (1.7.3) @@ -391,6 +394,7 @@ PLATFORMS DEPENDENCIES awesome_print (~> 1.9) aws-sdk-s3 (~> 1.61) + aws-sdk-sns (~> 1.72) aws-ses-v4 byebug (~> 11.1) capybara (~> 3.38) @@ -452,4 +456,4 @@ RUBY VERSION ruby 2.7.6p219 BUNDLED WITH - 2.4.15 + 2.4.13 diff --git a/INSTALLATION.md b/INSTALLATION.md index edc5c4639..e54c50de4 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -118,11 +118,11 @@ You will need to set the Redis connection details there too. If you've followed the sample file should already contain the correct values for you, but if you've customised your setup you'll need to correct them. +You'll also need to copy the Active Storage configuration from `config/storage.sample.yml` to `config/storage.yml`. + If you are using MariaDB instead of MySQL, you will need to replace all occurrences of `utf8mb4_0900_ai_ci` with `utf8mb4_unicode_ci` in `db/schema.rb`. -You'll also need to copy the Active Storage configuration from `config/storage.sample.yml` to `config/storage.yml`. - Set up the database: rails db:create diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js index 76c4c2678..d9cd43a1a 100644 --- a/app/assets/javascripts/posts.js +++ b/app/assets/javascripts/posts.js @@ -93,6 +93,13 @@ $(() => { tags: true }); + /** + * Attempts to save a draft with a given body + * @param {string} postText draft text + * @param {JQuery} $field body input element + * @param {boolean} [manual] whether manual draft saving is enabled + * @returns {Promise} + */ const saveDraft = async (postText, $field, manual = false) => { const autosavePref = await QPixel.preference('autosave', true); if (autosavePref !== 'on' && !manual) { @@ -111,10 +118,15 @@ $(() => { path: location.pathname }) }); + if (resp.status === 200) { - const $el = $(`· draft saved`); - $field.parents('.widget').find('.js-post-field-footer').append($el); - $el.fadeOut(1500, function () { $(this).remove() }); + const $statusEl = $field.parents('.widget').find('.js-post-draft-status'); + + $statusEl.removeClass('transparent'); + + setTimeout(() => { + $statusEl.addClass('transparent'); + }, 1500); } }; diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss index a1a548af6..67e911056 100644 --- a/app/assets/stylesheets/posts.scss +++ b/app/assets/stylesheets/posts.scss @@ -89,7 +89,25 @@ h1 .badge.is-tag.is-master-tag { width: calc(100% + 2px); + .widget--footer { - margin-bottom: 0; + border-top: none; + align-items: center; + margin: 0; + + &.mdhint { + display: flex; + flex-wrap: wrap; + gap: 1em 0; + justify-content: space-between; + + & > * { + padding: 0; + } + } + + & > .draft-status { + text-align: center; + transition: opacity 0.5s ease-in-out; + } } } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index b90f749f8..2ed9f314b 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -284,4 +284,8 @@ span.spoiler { .clearfix { overflow: hidden; -} \ No newline at end of file +} + +.transparent { + opacity: 0; +} diff --git a/app/controllers/email_logs_controller.rb b/app/controllers/email_logs_controller.rb new file mode 100644 index 000000000..bebe90f40 --- /dev/null +++ b/app/controllers/email_logs_controller.rb @@ -0,0 +1,24 @@ +class EmailLogsController < ApplicationController + skip_forgery_protection only: [:log] + skip_before_action :set_globals, only: [:log] + skip_before_action :distinguish_fake_community, only: [:log] + skip_before_action :enforce_signed_in, only: [:log] + + def log + message_type = request.headers['X-Amz-SNS-Message-Type'] + if ['SubscriptionConfirmation', 'Notification'].include? message_type + verifier = Aws::SNS::MessageVerifier.new + body = request.body.read + if verifier.authentic? body + aws_data = JSON.parse body + message_data = JSON.parse aws_data['Message'] + log_type = message_data['notificationType'] + destination = message_data['mail']['destination'].join(', ') + EmailLog.create(log_type: log_type, destination: destination, data: aws_data['Message']) + render plain: 'OK' + else + render plain: "You're not AWS. Go away.", status: 401 + end + end + end +end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 501c60a4a..134c5dbff 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -32,13 +32,15 @@ def category @tag_set.tags.where(excerpt: '').or(@tag_set.tags.where(excerpt: nil)) .order(Arel.sql('COUNT(posts.id) DESC')) else - @tag_set.tags.order(Arel.sql('COUNT(posts.id) DESC')) + @tag_set&.tags&.order(Arel.sql('COUNT(posts.id) DESC')) end - @count = @tags.count + @count = @tags&.size || 0 table = params[:hierarchical].present? ? 'tags_paths' : 'tags' - @tags = @tags.left_joins(:posts).group(Arel.sql("#{table}.id")) - .select(Arel.sql("#{table}.*, COUNT(DISTINCT IF(posts.deleted = 0, posts.id, NULL)) AS post_count")) - .paginate(per_page: 96, page: params[:page]) + if @count.positive? + @tags = @tags.left_joins(:posts).group(Arel.sql("#{table}.id")) + .select(Arel.sql("#{table}.*, COUNT(DISTINCT IF(posts.deleted = 0, posts.id, NULL)) AS post_count")) + .paginate(per_page: 96, page: params[:page]) + end end def show diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ffb99d344..8278f7271 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -174,7 +174,7 @@ def set_preference end def posts - @posts = if current_user&.privilege?('flag_curate') + @posts = if current_user&.privilege?('flag_curate') || @user == current_user Post.all else Post.undeleted @@ -579,7 +579,7 @@ def vote_summary def avatar respond_to do |format| format.png do - size = params[:size]&.to_i&.positive? ? params[:size]&.to_i : 64 + size = params[:size]&.to_i&.positive? ? [params[:size]&.to_i, 256].min : 64 send_data helpers.user_auto_avatar(size, user: @user).to_blob, type: 'image/png', disposition: 'inline' end end diff --git a/app/helpers/email_logs_helper.rb b/app/helpers/email_logs_helper.rb new file mode 100644 index 000000000..9b08eeb7a --- /dev/null +++ b/app/helpers/email_logs_helper.rb @@ -0,0 +1,2 @@ +module EmailLogsHelper +end diff --git a/app/models/email_log.rb b/app/models/email_log.rb new file mode 100644 index 000000000..9b171ddbc --- /dev/null +++ b/app/models/email_log.rb @@ -0,0 +1,2 @@ +class EmailLog < ApplicationRecord +end diff --git a/app/views/abilities/show.html.erb b/app/views/abilities/show.html.erb index c06d2131e..a34e29ca2 100644 --- a/app/views/abilities/show.html.erb +++ b/app/views/abilities/show.html.erb @@ -49,20 +49,23 @@ <% unless @ability.manual? %> <% unless params[:thresholds].nil? %>
-

You need to reach these thresholds to earn the ability:

- + <% if @user.id == current_user&.id %> +

You need to reach these thresholds to earn the ability:

+ <% else %> +

<%= @user.username %> needs to reach these thresholds to earn the ability:

+ <% end %> <% unless @ability.post_score_threshold.nil? %> <% post_score_percent = (linearize_progress(@user.community_user.post_score) / linearize_progress(@ability.post_score_threshold) * 100).to_i %>

Post score threshold

<% if post_score_percent < 100 %> -

You need to have more well-received posts.

+

Need to have more well-received posts.

<%= post_score_percent %>%
<% else %> -

You need to have many well-received posts.

+

Need to have many well-received posts.

100%
@@ -75,13 +78,13 @@

Edit score threshold

<% if edit_score_percent < 100 %> -

You need to have more accepted suggested edits.

+

Need to have more accepted suggested edits.

<%= edit_score_percent %>%
<% else %> -

You need to have many accepted suggested edits.

+

Need to have many accepted suggested edits.

100%
@@ -94,13 +97,13 @@

Flag score threshold

<% if flag_score_percent < 100 %> -

You need to have more helpful flags.

+

Need to have more helpful flags.

<%= flag_score_percent %>%
<% else %> -

You need to have many helpful flags.

+

Need to have many helpful flags.

100%
diff --git a/app/views/flags/_flag.html.erb b/app/views/flags/_flag.html.erb index 3ce6aea42..c98e81280 100644 --- a/app/views/flags/_flag.html.erb +++ b/app/views/flags/_flag.html.erb @@ -21,7 +21,7 @@

<%= flag.post_flag_type&.name || 'Flag reason' %>: <%= flag.reason %> — - <%= user_link flag.user %> + <%= user_link flag.user %> at <%= flag.created_at.iso8601 %>

<% if escalation %> @@ -30,6 +30,9 @@ Escalation reason: <%= flag.escalation_comment %> — <%= user_link flag.escalated_by %> <%= flag.escalated_at.iso8601 %>

+

+ Attention: The reply to the flag will be shown to the flagger, not the mod escalating this flag. Do not share sensitive, mod-only information. +

<% end %> <% if controls %> @@ -50,4 +53,4 @@
<% end %> -
\ No newline at end of file + diff --git a/app/views/flags/_handled.html.erb b/app/views/flags/_handled.html.erb index c82307cea..e9e65c75c 100644 --- a/app/views/flags/_handled.html.erb +++ b/app/views/flags/_handled.html.erb @@ -9,7 +9,7 @@

<%= flag.post_flag_type&.name || 'Flag reason' %>: <%= flag.reason %> — - <%= user_link flag.user %> + <%= user_link flag.user %> at <%= flag.created_at.iso8601 %> (<%= link_to 'history', flag_history_path(flag.user) %>)

@@ -24,8 +24,9 @@ — <%= link_to user_path(flag.handled_by) do %> <%= rtl_safe_username(flag.handled_by) %> - <% end %> + <% end %> + handled at <%= flag.handled_at.iso8601 %> <% end %>

- \ No newline at end of file + diff --git a/app/views/flags/handled.html.erb b/app/views/flags/handled.html.erb index 320ac30dd..9e0c3d0ed 100644 --- a/app/views/flags/handled.html.erb +++ b/app/views/flags/handled.html.erb @@ -13,4 +13,6 @@ <% @flags.each do |flag| %> <%= render 'handled', flag: flag %> -<% end %> \ No newline at end of file +<% end %> + +<%= will_paginate @flags, renderer: BootstrapPagination::Rails %> diff --git a/app/views/posts/_mdhint.html.erb b/app/views/posts/_mdhint.html.erb index 22dfa8372..27c264ddd 100644 --- a/app/views/posts/_mdhint.html.erb +++ b/app/views/posts/_mdhint.html.erb @@ -15,8 +15,11 @@ max_length = (defined?(max_length) ? max_length : nil) || 30_000 %> -
- We support Markdown for posts: - **bold**, *italics*, `code`, two newlines for paragraphs - <%= render 'shared/char_count', type: 'post-body', cur: cur_length, min: min_length, max: max_length %> +
+ Markdown support for posts: + **bold**, *italics*, `code`, 2 newlines for paragraphs + draft saved +
+ <%= render 'shared/char_count', type: 'post-body', cur: cur_length, min: min_length, max: max_length %> +
diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index 94baae739..b81569e90 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -34,4 +34,6 @@
You don't have a high enough trust level to post in the <%= @category.name %> category.
+

Not where you meant to post? See <%= link_to 'Categories', categories_path %>

+ <% end %> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index d5a6e7f89..5846533fd 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -24,9 +24,14 @@
<%= link_to 'Score', request.params.merge(sort: 'score'), - class: "button is-muted is-outlined #{params[:sort].nil? || params[:sort] == 'score' ? 'is-active' : ''}" %> + class: "button is-muted is-outlined #{params[:sort].nil? || params[:sort] == 'score' ? 'is-active' : ''}", + title: 'highest score first (not the same as net votes)' %> <%= link_to 'Active', request.params.merge(sort: 'active'), - class: "button is-muted is-outlined #{params[:sort] == 'active' ? 'is-active' : ''}" %> + class: "button is-muted is-outlined #{params[:sort] == 'active' ? 'is-active' : ''}", + title: 'most recent changes first: new answers, edits, delete/undelete' %> + <%= link_to 'Age', request.params.merge(sort: 'age'), + class: "button is-muted is-outlined #{params[:sort] == 'age' ? 'is-active' : ''}", + title: 'newest posts first (ignores other activity)' %>
diff --git a/app/views/shared/_body_field.html.erb b/app/views/shared/_body_field.html.erb index 08b68f14d..2552d739d 100644 --- a/app/views/shared/_body_field.html.erb +++ b/app/views/shared/_body_field.html.erb @@ -39,7 +39,9 @@ <%= render 'shared/markdown_tools' %> <%= f.text_area field_name, **({ class: classes, rows: 15, placeholder: 'Start typing your post...' }).merge(value), data: { character_count: ".js-character-count-post-body" } %> - <%= render 'posts/mdhint', cur_length: cur_length, min_length: min_length, max_length: max_length %> + <%= render 'posts/mdhint', cur_length: value[:value]&.length || cur_length, + min_length: min_length, + max_length: max_length %>
<%= hidden_field_tag "__html", nil, class: 'js-post-html' %> diff --git a/app/views/tags/_list.html.erb b/app/views/tags/_list.html.erb index cc7e4ea91..fb46962ba 100644 --- a/app/views/tags/_list.html.erb +++ b/app/views/tags/_list.html.erb @@ -4,7 +4,7 @@ <% topic_ids = @category&.topic_tag_ids %> <% ApplicationRecord.with_lax_group_rules do %> - <% @tags.each do |tag| %> + <% @tags&.each do |tag| %> <% required = required_ids&.include?(tag.id) ? 'is-filled' : '' %> <% topic = topic_ids&.include?(tag.id) ? 'is-outlined' : '' %> <% moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : '' %> @@ -12,4 +12,4 @@ <%= render 'tag', category: @category, tag: tag, classes: classes %> <% end %> <% end %> - \ No newline at end of file + diff --git a/app/views/tags/category.html.erb b/app/views/tags/category.html.erb index 7bd695933..01c8d29a3 100644 --- a/app/views/tags/category.html.erb +++ b/app/views/tags/category.html.erb @@ -2,6 +2,14 @@

Tags used in <%= @category.name %>

+<% ApplicationRecord.with_lax_group_rules do %> + +<% if @tags == nil %> +

This category is missing its tag set. Set this in the Category settings (admin).

+<% end %> + +<% unless @tags == nil %> + <% if current_user&.is_moderator || check_your_privilege('edit_tags') %> <%= link_to 'New', new_tag_path(id: @category.id), class: 'button is-muted is-outlined', 'aria-label': 'Create new tag' %> <% end %> @@ -37,7 +45,7 @@ <%= render 'list' %> -<% if @tags.size == 0 %> +<% if @tags&.size == 0 %> <% if params[:q].present? %>

No results for <%= params[:q] %>

<% else %> @@ -46,3 +54,8 @@ <% end %> <%= will_paginate @tags, renderer: BootstrapPagination::Rails %> + +<% end %> <%# unless @tags == nil %> + +<% end %> <%# ApplicationRecord.with_lax_group_rules %> + diff --git a/app/views/tags/show.html.erb b/app/views/tags/show.html.erb index 9dade76fa..715f9a062 100644 --- a/app/views/tags/show.html.erb +++ b/app/views/tags/show.html.erb @@ -94,9 +94,15 @@
<%= short_number_to_human post_count, precision: 1, significant: false %> <%= 'post'.pluralize(post_count) %> - <%= link_to 'Subscribe', - new_subscription_path(type: 'tag', qualifier: @tag.name, return_to: request.path), - class: 'button is-outlined', 'aria-label': "Subscribe to tag #{@tag.name}" %> + + <% if user_signed_in? && current_user&.subscriptions.where(type: 'tag', qualifier: @tag.name).exists? %> + <%= link_to 'Subscribed', subscriptions_path(current_user), + class: 'button is-outlined', 'aria-label': "Subscribed to tag #{@tag.name}" %> + <% else %> + <%= link_to 'Subscribe', + new_subscription_path(type: 'tag', qualifier: @tag.name, return_to: request.path), + class: 'button is-outlined', 'aria-label': "Subscribe to tag #{@tag.name}" %> + <% end %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index a70f5e5da..1d5067773 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -85,7 +85,7 @@ <% if @posts.size > 0 %> <%= link_to user_posts_path(@user), class: "button is-muted", 'aria-label': "View all posts by #{rtl_safe_username(@user)}" do %> - See all » + See all <%= @posts.count %> » <% end %> <% end %>
@@ -98,7 +98,7 @@ <% end %> <%= link_to user_posts_path(@user), class: "button is-muted", 'aria-label': "View all posts by #{rtl_safe_username(@user)}" do %> - See all » + See all <%= @posts.count %> » <% end %> <% end %> @@ -147,6 +147,14 @@ <%= @user.metric 'E' %> + + <% if current_user&.id == @user.id || current_user&.is_moderator %> + + + + +
User since <%= @user.created_at %>
+ <% end %> <% unless @abilities.empty? %>

Earned Abilities

diff --git a/config/environments/development.rb b/config/environments/development.rb index 2c58a27a0..5c40b6d06 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -34,6 +34,9 @@ # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local + # Allow ngrok connections to dev server + config.hosts << /[a-z0-9-.]+\.ngrok-free\.app/ + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false config.action_mailer.delivery_method = :ses diff --git a/config/routes.rb b/config/routes.rb index d71df83c2..f4066cdc4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -348,6 +348,10 @@ end end + scope 'emails' do + post 'log', to: 'email_logs#log', as: :create_email_log + end + get '403', to: 'errors#forbidden' get '404', to: 'errors#not_found' get '409', to: 'errors#conflict' diff --git a/config/schedule.rb b/config/schedule.rb index a9dd5e5aa..18fbf1fe7 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -9,3 +9,7 @@ every 6.hours do runner 'scripts/recalc_abilities.rb' end + +every 1.day, at: '02:10' do + runner 'scripts/prune_email_logs.rb' +end diff --git a/db/migrate/20240405113618_create_email_logs.rb b/db/migrate/20240405113618_create_email_logs.rb new file mode 100644 index 000000000..ecea1a21d --- /dev/null +++ b/db/migrate/20240405113618_create_email_logs.rb @@ -0,0 +1,11 @@ +class CreateEmailLogs < ActiveRecord::Migration[7.0] + def change + create_table :email_logs do |t| + t.string :log_type + t.string :destination + t.text :data + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7055af307..baac35e3e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_08_17_213150) do +ActiveRecord::Schema[7.0].define(version: 2024_04_05_113618) do create_table "abilities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "community_id" t.string "name" @@ -231,6 +231,14 @@ t.index ["user_id"], name: "index_community_users_on_user_id" end + create_table "email_logs", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.string "log_type" + t.string "destination" + t.text "data" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "error_logs", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "community_id" t.bigint "user_id" diff --git a/db/seeds/abilities/edit_posts.html b/db/seeds/abilities/edit_posts.html index ecb1741a4..ff0fdff83 100644 --- a/db/seeds/abilities/edit_posts.html +++ b/db/seeds/abilities/edit_posts.html @@ -1,9 +1,9 @@

What does this ability allow me to do?

This ability allows you to unilaterally edit posts, as well as review suggested edits from other users.

-

Edits should be used to improve posts, including edits improving spelling, grammar, and formatting, as well as fixes for missing alt text, broken images and links, and other improvements. Edits should, however, respect the goal of the original poster.

+

Edits should be used to improve posts, including edits improving spelling, grammar, and formatting, as well as fixes for missing alt text, broken images and links, and other improvements. Edits should, however, preserve the meaning of the post.

Edit post button

How do I earn this ability?

-

To earn this ability, you need to have at least a 95% approval rate for your suggested edits, with a hard minimum of 30 approved suggested edits (these numbers may vary from site to site).

\ No newline at end of file +

To earn this ability, you need to have at least a 95% approval rate for your suggested edits, with a hard minimum of 30 approved suggested edits (these numbers may vary from community to community).

diff --git a/db/seeds/abilities/everyone.html b/db/seeds/abilities/everyone.html index be547ccca..8f112ef81 100644 --- a/db/seeds/abilities/everyone.html +++ b/db/seeds/abilities/everyone.html @@ -1,9 +1,9 @@

What does this ability allow me to do?

-

This ability allows you to posts 3 top-level posts (questions and articles) a day, and to post 20 answers a day.

+

This ability allows you to post 3 top-level posts (questions and articles) per day, and to post 20 answers per day.

-

This ability also allows you to raise 15 flags on posts a day.

+

This ability also allows you to raise 15 flags on posts per day.

How do I earn this ability?

-

This ability is automatically granted to you when you create an account.

\ No newline at end of file +

This ability is automatically granted to you when you create an account.

diff --git a/db/seeds/post_flag_types.yml b/db/seeds/post_flag_types.yml index c8747bbe0..5c3204aca 100644 --- a/db/seeds/post_flag_types.yml +++ b/db/seeds/post_flag_types.yml @@ -24,4 +24,11 @@ This question has been asked before and has already been answered. It should be marked as a duplicate. confidential: false active: true - post_type_id: <%= Question.post_type_id %> \ No newline at end of file + post_type_id: <%= Question.post_type_id %> + +- name: generated by AI + description: > + This post appears to contain AI-generated content (such as ChatGPT) without [attribution](/help/referencing). + confidential: false + active: true + post_type_id: null diff --git a/scripts/prune_email_logs.rb b/scripts/prune_email_logs.rb new file mode 100644 index 000000000..0336c9e0c --- /dev/null +++ b/scripts/prune_email_logs.rb @@ -0,0 +1 @@ +EmailLog.where('created_at < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 MONTH)').destroy_all diff --git a/test/controllers/email_logs_controller_test.rb b/test/controllers/email_logs_controller_test.rb new file mode 100644 index 000000000..9addb0be1 --- /dev/null +++ b/test/controllers/email_logs_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class EmailLogsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/email_logs.yml b/test/fixtures/email_logs.yml new file mode 100644 index 000000000..7ca782a8c --- /dev/null +++ b/test/fixtures/email_logs.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + log_type: MyString + destination: MyString + data: MyText + +two: + log_type: MyString + destination: MyString + data: MyText diff --git a/test/models/email_log_test.rb b/test/models/email_log_test.rb new file mode 100644 index 000000000..12c90407f --- /dev/null +++ b/test/models/email_log_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class EmailLogTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end