Skip to content

Commit a09cf9f

Browse files
committed
remove sheets integration
1 parent 6ff08b5 commit a09cf9f

File tree

17 files changed

+1058
-2
lines changed

17 files changed

+1058
-2
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
module Admin
2+
class IgnoreListsController < BaseController
3+
before_action :set_ignore_list, only: [:edit, :update, :destroy]
4+
5+
def index
6+
@ignore_lists = IgnoreList.all
7+
8+
# Filter by list type
9+
if params[:list_type].present?
10+
@ignore_lists = @ignore_lists.where(list_type: params[:list_type])
11+
end
12+
13+
# Search functionality
14+
if params[:search].present?
15+
@ignore_lists = @ignore_lists.where('value ILIKE ?', "%#{params[:search]}%")
16+
end
17+
18+
# Sorting
19+
@ignore_lists = @ignore_lists.order(created_at: :desc)
20+
21+
# Pagination
22+
@ignore_lists = @ignore_lists.page(params[:page]).per(25)
23+
24+
respond_to do |format|
25+
format.html
26+
format.turbo_stream
27+
end
28+
end
29+
30+
def new
31+
@ignore_list = IgnoreList.new(list_type: params[:list_type])
32+
end
33+
34+
def create
35+
@ignore_list = IgnoreList.new(ignore_list_params)
36+
37+
if @ignore_list.save
38+
redirect_to admin_ignore_lists_path, notice: 'Ignore list entry was successfully created.'
39+
else
40+
render :new, status: :unprocessable_entity
41+
end
42+
end
43+
44+
def edit
45+
end
46+
47+
def update
48+
if @ignore_list.update(ignore_list_params)
49+
redirect_to admin_ignore_lists_path, notice: 'Ignore list entry was successfully updated.'
50+
else
51+
render :edit, status: :unprocessable_entity
52+
end
53+
end
54+
55+
def destroy
56+
@ignore_list.destroy
57+
redirect_to admin_ignore_lists_path, notice: 'Ignore list entry was successfully deleted.'
58+
end
59+
60+
# Bulk import action
61+
def bulk_import
62+
if request.post?
63+
results = process_bulk_import(params[:import_data], params[:list_type])
64+
65+
if results[:errors].empty?
66+
redirect_to admin_ignore_lists_path, notice: "Successfully imported #{results[:created].count} entries."
67+
else
68+
redirect_to admin_ignore_lists_path,
69+
alert: "Imported #{results[:created].count} entries. #{results[:errors].count} errors occurred."
70+
end
71+
end
72+
end
73+
74+
private
75+
76+
def set_ignore_list
77+
@ignore_list = IgnoreList.find(params[:id])
78+
end
79+
80+
def ignore_list_params
81+
params.require(:ignore_list).permit(:list_type, :value, :notes)
82+
end
83+
84+
def process_bulk_import(import_data, list_type)
85+
results = { created: [], errors: [] }
86+
87+
return results if import_data.blank? || list_type.blank?
88+
89+
# Split by newlines and process each line
90+
import_data.split("\n").each do |line|
91+
value = line.strip
92+
next if value.blank?
93+
94+
ignore_list = IgnoreList.new(list_type: list_type, value: value)
95+
96+
if ignore_list.save
97+
results[:created] << ignore_list
98+
else
99+
results[:errors] << { value: value, errors: ignore_list.errors.full_messages }
100+
end
101+
end
102+
103+
results
104+
end
105+
end
106+
end
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
module Api
2+
module V1
3+
class IgnoreListsController < ApplicationController
4+
before_action :authenticate_user!
5+
before_action :require_admin!, except: [:index]
6+
before_action :set_ignore_list, only: [:show, :update, :destroy]
7+
8+
# GET /api/v1/ignore_lists
9+
def index
10+
@ignore_lists = IgnoreList.all
11+
12+
# Filter by list_type if provided
13+
if params[:list_type].present?
14+
@ignore_lists = @ignore_lists.where(list_type: params[:list_type])
15+
end
16+
17+
# Search functionality
18+
if params[:search].present?
19+
@ignore_lists = @ignore_lists.where('value ILIKE ?', "%#{params[:search]}%")
20+
end
21+
22+
# Pagination
23+
@ignore_lists = @ignore_lists.page(params[:page]).per(params[:per_page] || 100)
24+
25+
render json: {
26+
ignore_lists: @ignore_lists.as_json,
27+
meta: pagination_meta(@ignore_lists)
28+
}
29+
end
30+
31+
# GET /api/v1/ignore_lists/by_type
32+
# Returns grouped ignore lists by type for easy consumption
33+
def by_type
34+
ignore_lists = IgnoreList.all
35+
36+
grouped = {
37+
twitch_users: ignore_lists.twitch_users.pluck(:value),
38+
discord_users: ignore_lists.discord_users.pluck(:value),
39+
urls: ignore_lists.urls.pluck(:value),
40+
domains: ignore_lists.domains.pluck(:value)
41+
}
42+
43+
render json: grouped
44+
end
45+
46+
# GET /api/v1/ignore_lists/:id
47+
def show
48+
render json: @ignore_list
49+
end
50+
51+
# POST /api/v1/ignore_lists
52+
def create
53+
@ignore_list = IgnoreList.new(ignore_list_params)
54+
55+
if @ignore_list.save
56+
render json: @ignore_list, status: :created
57+
else
58+
render json: { errors: @ignore_list.errors.full_messages }, status: :unprocessable_entity
59+
end
60+
end
61+
62+
# POST /api/v1/ignore_lists/bulk_create
63+
# Allows creating multiple ignore list entries at once
64+
def bulk_create
65+
results = {
66+
created: [],
67+
errors: []
68+
}
69+
70+
bulk_params[:entries].each do |entry|
71+
ignore_list = IgnoreList.new(
72+
list_type: entry[:list_type],
73+
value: entry[:value],
74+
notes: entry[:notes]
75+
)
76+
77+
if ignore_list.save
78+
results[:created] << ignore_list
79+
else
80+
results[:errors] << {
81+
value: entry[:value],
82+
errors: ignore_list.errors.full_messages
83+
}
84+
end
85+
end
86+
87+
render json: results, status: :created
88+
end
89+
90+
# PATCH/PUT /api/v1/ignore_lists/:id
91+
def update
92+
if @ignore_list.update(ignore_list_params)
93+
render json: @ignore_list
94+
else
95+
render json: { errors: @ignore_list.errors.full_messages }, status: :unprocessable_entity
96+
end
97+
end
98+
99+
# DELETE /api/v1/ignore_lists/:id
100+
def destroy
101+
@ignore_list.destroy
102+
head :no_content
103+
end
104+
105+
# DELETE /api/v1/ignore_lists/bulk_delete
106+
# Allows deleting multiple ignore list entries at once
107+
def bulk_delete
108+
ids = params[:ids] || []
109+
deleted_count = IgnoreList.where(id: ids).destroy_all.count
110+
111+
render json: { deleted_count: deleted_count }
112+
end
113+
114+
private
115+
116+
def set_ignore_list
117+
@ignore_list = IgnoreList.find(params[:id])
118+
end
119+
120+
def ignore_list_params
121+
params.require(:ignore_list).permit(:list_type, :value, :notes)
122+
end
123+
124+
def bulk_params
125+
params.permit(entries: [:list_type, :value, :notes])
126+
end
127+
128+
def require_admin!
129+
head :forbidden unless current_user.admin?
130+
end
131+
end
132+
end
133+
end

app/models/ignore_list.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class IgnoreList < ApplicationRecord
2+
# List types
3+
LIST_TYPES = %w[twitch_user discord_user url domain].freeze
4+
5+
# Validations
6+
validates :list_type, presence: true, inclusion: { in: LIST_TYPES }
7+
validates :value, presence: true, uniqueness: { scope: :list_type }
8+
9+
# Scopes
10+
scope :twitch_users, -> { where(list_type: 'twitch_user') }
11+
scope :discord_users, -> { where(list_type: 'discord_user') }
12+
scope :urls, -> { where(list_type: 'url') }
13+
scope :domains, -> { where(list_type: 'domain') }
14+
15+
# Normalize values before saving
16+
before_validation :normalize_value
17+
18+
private
19+
20+
def normalize_value
21+
return unless value.present?
22+
23+
self.value = case list_type
24+
when 'twitch_user', 'discord_user'
25+
value.strip.downcase
26+
when 'url'
27+
normalize_url(value.strip)
28+
when 'domain'
29+
value.strip.downcase.gsub(/^www\./, '')
30+
else
31+
value.strip
32+
end
33+
end
34+
35+
def normalize_url(url)
36+
# Add protocol if missing
37+
url = "https://#{url}" unless url.match?(%r{^https?://})
38+
39+
# Remove trailing slashes
40+
url.gsub(/\/+$/, '')
41+
rescue StandardError
42+
url
43+
end
44+
end
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<%= form_with model: [:admin, ignore_list], local: true do |form| %>
2+
<% if ignore_list.errors.any? %>
3+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
4+
<h4 class="font-bold">Please fix the following errors:</h4>
5+
<ul class="list-disc list-inside">
6+
<% ignore_list.errors.full_messages.each do |message| %>
7+
<li><%= message %></li>
8+
<% end %>
9+
</ul>
10+
</div>
11+
<% end %>
12+
13+
<div class="space-y-4">
14+
<div>
15+
<%= form.label :list_type, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
16+
<%= form.select :list_type,
17+
options_for_select(IgnoreList::LIST_TYPES.map { |t| [t.humanize, t] }, ignore_list.list_type),
18+
{ prompt: "Select a type..." },
19+
class: "form-select mt-1",
20+
required: true %>
21+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
22+
Choose the type of entry to ignore
23+
</p>
24+
</div>
25+
26+
<div>
27+
<%= form.label :value, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
28+
<%= form.text_field :value,
29+
class: "form-input mt-1",
30+
placeholder: "Enter the value to ignore...",
31+
required: true %>
32+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
33+
<span class="list-type-hint" data-type="twitch_user" style="display: none;">
34+
Enter the Twitch username (without @)
35+
</span>
36+
<span class="list-type-hint" data-type="discord_user" style="display: none;">
37+
Enter the Discord username#discriminator or just username
38+
</span>
39+
<span class="list-type-hint" data-type="url" style="display: none;">
40+
Enter the full URL to block
41+
</span>
42+
<span class="list-type-hint" data-type="domain" style="display: none;">
43+
Enter the domain name (e.g., example.com)
44+
</span>
45+
</p>
46+
</div>
47+
48+
<div>
49+
<%= form.label :notes, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
50+
<%= form.text_area :notes,
51+
rows: 3,
52+
class: "form-textarea mt-1",
53+
placeholder: "Optional notes about why this entry is ignored..." %>
54+
</div>
55+
</div>
56+
57+
<div class="mt-6 flex justify-end gap-3">
58+
<%= link_to "Cancel", admin_ignore_lists_path,
59+
class: "btn btn-outline",
60+
data: { turbo_frame: "_top" } %>
61+
<%= form.submit class: "btn btn-primary" %>
62+
</div>
63+
<% end %>
64+
65+
<script>
66+
// Show appropriate hint based on selected type
67+
document.addEventListener('turbo:load', function() {
68+
const typeSelect = document.querySelector('#ignore_list_list_type');
69+
const hints = document.querySelectorAll('.list-type-hint');
70+
71+
if (typeSelect) {
72+
function updateHint() {
73+
hints.forEach(hint => {
74+
hint.style.display = hint.dataset.type === typeSelect.value ? 'block' : 'none';
75+
});
76+
}
77+
78+
typeSelect.addEventListener('change', updateHint);
79+
updateHint(); // Show initial hint
80+
}
81+
});
82+
</script>

0 commit comments

Comments
 (0)