style(ntfy): fix bracket-spacing lint offenses
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
module Admin
|
||||
class BaseController < ActionController::Base
|
||||
include Pagy::Backend
|
||||
include AdminAuthentication
|
||||
|
||||
# Enable session and flash for admin controllers
|
||||
# (needed because the app is in API-only mode)
|
||||
@@ -9,22 +10,12 @@ module Admin
|
||||
layout "admin"
|
||||
before_action :require_admin
|
||||
|
||||
helper_method :current_admin, :logged_in?
|
||||
|
||||
private
|
||||
|
||||
def current_admin
|
||||
@current_admin ||= AdminUser.find_by(id: session[:admin_id]) if session[:admin_id]
|
||||
end
|
||||
helper_method :current_admin
|
||||
|
||||
def logged_in?
|
||||
current_admin.present?
|
||||
end
|
||||
helper_method :logged_in?
|
||||
|
||||
def require_admin
|
||||
unless logged_in?
|
||||
redirect_to admin_login_path, alert: "Please log in to continue"
|
||||
end
|
||||
redirect_to admin_login_path, alert: "Please log in to continue" unless logged_in?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
module Admin
|
||||
class SessionsController < ActionController::Base
|
||||
include AdminAuthentication
|
||||
|
||||
layout "admin"
|
||||
|
||||
# CSRF protection is enabled by default in ActionController::Base
|
||||
# We need it for the create action but not for the new (GET) action
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
helper_method :current_admin, :logged_in?
|
||||
|
||||
def new
|
||||
redirect_to admin_dashboard_path if current_admin
|
||||
end
|
||||
@@ -27,12 +31,5 @@ module Admin
|
||||
session.delete(:admin_id)
|
||||
redirect_to admin_login_path, notice: "You have been logged out"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_admin
|
||||
@current_admin ||= AdminUser.find_by(id: session[:admin_id]) if session[:admin_id]
|
||||
end
|
||||
helper_method :current_admin
|
||||
end
|
||||
end
|
||||
|
||||
11
app/controllers/concerns/admin_authentication.rb
Normal file
11
app/controllers/concerns/admin_authentication.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module AdminAuthentication
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def current_admin
|
||||
@current_admin ||= AdminUser.find_by(id: session[:admin_id]) if session[:admin_id]
|
||||
end
|
||||
|
||||
def logged_in?
|
||||
current_admin.present?
|
||||
end
|
||||
end
|
||||
@@ -1,9 +1,2 @@
|
||||
module AdminHelper
|
||||
def current_admin
|
||||
@current_admin ||= AdminUser.find_by(id: session[:admin_id]) if session[:admin_id]
|
||||
end
|
||||
|
||||
def logged_in?
|
||||
current_admin.present?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
module ApplicationHelper
|
||||
include Pagy::Frontend
|
||||
|
||||
# Admin authentication helpers
|
||||
def current_admin
|
||||
@current_admin ||= AdminUser.find_by(id: session[:admin_id]) if session[:admin_id]
|
||||
end
|
||||
|
||||
def logged_in?
|
||||
current_admin.present?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ class CheckGatewayHealthJob < ApplicationJob
|
||||
title: "Gateway offline",
|
||||
message: "#{name} (#{device_id}) went offline — no heartbeat for 2+ minutes",
|
||||
priority: 4,
|
||||
tags: ["rotating_light"])
|
||||
tags: [ "rotating_light" ])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ class ApiKey < ApplicationRecord
|
||||
title: "API key revoked",
|
||||
message: "API key '#{name}' (#{key_prefix}...) was revoked",
|
||||
priority: 3,
|
||||
tags: ["key", "no_entry"])
|
||||
tags: [ "key", "no_entry" ])
|
||||
end
|
||||
|
||||
# Deactivate expired keys
|
||||
|
||||
@@ -45,7 +45,7 @@ class Gateway < ApplicationRecord
|
||||
title: "Gateway online",
|
||||
message: "#{name} (#{device_id}) came back online",
|
||||
priority: 3,
|
||||
tags: ["white_check_mark"],
|
||||
tags: [ "white_check_mark" ],
|
||||
click: nil)
|
||||
end
|
||||
end
|
||||
@@ -60,7 +60,7 @@ class Gateway < ApplicationRecord
|
||||
title: "Gateway offline",
|
||||
message: "#{name} (#{device_id}) went offline",
|
||||
priority: 4,
|
||||
tags: ["rotating_light"])
|
||||
tags: [ "rotating_light" ])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class SmsMessage < ApplicationRecord
|
||||
title: "SMS delivered",
|
||||
message: "Message to #{phone_number} (#{message_id}) was delivered",
|
||||
priority: 2,
|
||||
tags: ["white_check_mark"])
|
||||
tags: [ "white_check_mark" ])
|
||||
end
|
||||
|
||||
# Mark message as failed
|
||||
@@ -71,7 +71,7 @@ class SmsMessage < ApplicationRecord
|
||||
title: "SMS failed",
|
||||
message: "Message to #{phone_number} (#{message_id}) failed#{error_msg ? ": #{error_msg}" : ''}",
|
||||
priority: 5,
|
||||
tags: ["x", "rotating_light"])
|
||||
tags: [ "x", "rotating_light" ])
|
||||
end
|
||||
|
||||
# Increment retry counter
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<div class="fade-up">
|
||||
<div class="text-center">
|
||||
<div class="mx-auto h-16 w-16 flex items-center justify-center rounded-full bg-blue-100">
|
||||
<i class="fas fa-sms text-3xl text-blue-600"></i>
|
||||
<div class="relative mx-auto h-20 w-20 flex items-center justify-center">
|
||||
<span class="beacon-ring absolute inset-0 rounded-full border border-sky-400/50"></span>
|
||||
<span class="beacon-ring delay-1 absolute inset-0 rounded-full border border-sky-400/40"></span>
|
||||
<span class="beacon-ring delay-2 absolute inset-0 rounded-full border border-sky-400/30"></span>
|
||||
<div class="relative flex h-14 w-14 items-center justify-center rounded-2xl bg-gradient-to-br from-sky-400 to-sky-600 shadow-lg shadow-sky-500/40">
|
||||
<i class="fas fa-sms text-2xl text-slate-950"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="mt-6 text-3xl font-bold tracking-tight text-gray-900">MySMSAPio Admin</h2>
|
||||
<p class="mt-2 text-sm text-gray-600">Sign in to your admin account</p>
|
||||
<h2 class="font-display mt-6 text-4xl font-extrabold tracking-tight text-white">MySMSAPio</h2>
|
||||
<p class="font-mono-tech mt-2 text-[11px] uppercase tracking-[0.35em] text-sky-400/80">Admin Console</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-white py-8 px-4 shadow-xl rounded-xl sm:px-10">
|
||||
<%= form_with url: admin_login_path, method: :post, local: true, class: "space-y-6" do |f| %>
|
||||
<div class="login-card fade-up d-1 mt-8 rounded-2xl px-8 py-9 sm:px-10">
|
||||
<%= form_with url: admin_login_path, method: :post, local: true, class: "space-y-5" do |f| %>
|
||||
<div>
|
||||
<%= label_tag :email, "Email address", class: "block text-sm font-medium text-gray-700" %>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-envelope text-gray-400"></i>
|
||||
<%= label_tag :email, "Email address", class: "font-mono-tech block text-[11px] uppercase tracking-wider text-slate-400" %>
|
||||
<div class="mt-2 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3.5">
|
||||
<i class="fas fa-envelope text-slate-500 text-sm"></i>
|
||||
</div>
|
||||
<%= email_field_tag :email, params[:email],
|
||||
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3",
|
||||
class: "field-input block w-full rounded-xl pl-11 pr-3 py-3 text-sm",
|
||||
placeholder: "admin@example.com",
|
||||
autofocus: true,
|
||||
required: true %>
|
||||
@@ -24,26 +29,27 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= label_tag :password, "Password", class: "block text-sm font-medium text-gray-700" %>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-lock text-gray-400"></i>
|
||||
<%= label_tag :password, "Password", class: "font-mono-tech block text-[11px] uppercase tracking-wider text-slate-400" %>
|
||||
<div class="mt-2 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3.5">
|
||||
<i class="fas fa-lock text-slate-500 text-sm"></i>
|
||||
</div>
|
||||
<%= password_field_tag :password, nil,
|
||||
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3",
|
||||
placeholder: "Enter your password",
|
||||
class: "field-input block w-full rounded-xl pl-11 pr-3 py-3 text-sm",
|
||||
placeholder: "••••••••••••",
|
||||
required: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="pt-2">
|
||||
<%= submit_tag "Sign in",
|
||||
class: "flex w-full justify-center rounded-lg bg-blue-600 px-4 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-all duration-200" %>
|
||||
class: "btn-accent flex w-full justify-center rounded-xl px-4 py-3.5 text-sm font-semibold text-slate-950" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-xs text-gray-500">
|
||||
<i class="fas fa-shield-alt"></i> Secure Admin Access
|
||||
<div class="fade-up d-2 mt-7 flex items-center justify-center gap-2 font-mono-tech text-[10px] uppercase tracking-[0.3em] text-slate-500">
|
||||
<i class="fas fa-shield-halved text-sky-500/70"></i>
|
||||
Secure · Encrypted · Local
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1395
docs/superpowers/plans/2026-07-28-ntfy-notifications-integration.md
Normal file
1395
docs/superpowers/plans/2026-07-28-ntfy-notifications-integration.md
Normal file
File diff suppressed because it is too large
Load Diff
12
test/fixtures/admin_users.yml
vendored
12
test/fixtures/admin_users.yml
vendored
@@ -1,13 +1,13 @@
|
||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
email: MyString
|
||||
password_digest: MyString
|
||||
name: MyString
|
||||
email: one@example.com
|
||||
password_digest: <%= BCrypt::Password.create("password123") %>
|
||||
name: Admin One
|
||||
last_login_at: 2025-10-20 10:51:35
|
||||
|
||||
two:
|
||||
email: MyString
|
||||
password_digest: MyString
|
||||
name: MyString
|
||||
email: two@example.com
|
||||
password_digest: <%= BCrypt::Password.create("password123") %>
|
||||
name: Admin Two
|
||||
last_login_at: 2025-10-20 10:51:35
|
||||
|
||||
48
test/integration/admin/sessions_flow_test.rb
Normal file
48
test/integration/admin/sessions_flow_test.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require "test_helper"
|
||||
|
||||
class AdminSessionsFlowTest < ActionDispatch::IntegrationTest
|
||||
def setup
|
||||
@admin = AdminUser.create!(name: "Site Admin", email: "admin@example.com", password: "supersecret123")
|
||||
end
|
||||
|
||||
test "login page renders the sign-in form" do
|
||||
get admin_login_path
|
||||
assert_response :success
|
||||
assert_match "MySMSAPio", response.body
|
||||
assert_match "Sign in", response.body
|
||||
end
|
||||
|
||||
test "login page displays the auth error message on failed login" do
|
||||
post admin_login_path, params: { email: "admin@example.com", password: "wrong-password" }
|
||||
assert_response :unprocessable_entity
|
||||
# The critical regression check: the alert must be present in the rendered HTML
|
||||
assert_match "Invalid email or password", response.body
|
||||
end
|
||||
|
||||
test "successful login redirects to the dashboard and shows welcome notice" do
|
||||
post admin_login_path, params: { email: "admin@example.com", password: "supersecret123" }
|
||||
assert_redirected_to admin_dashboard_path
|
||||
follow_redirect!
|
||||
assert_match "Welcome back", response.body
|
||||
end
|
||||
|
||||
test "login is case-insensitive for email" do
|
||||
post admin_login_path, params: { email: "ADMIN@example.com", password: "supersecret123" }
|
||||
assert_redirected_to admin_dashboard_path
|
||||
end
|
||||
|
||||
test "logging out clears the session and shows notice on the login page" do
|
||||
post admin_login_path, params: { email: "admin@example.com", password: "supersecret123" }
|
||||
delete admin_logout_path
|
||||
assert_redirected_to admin_login_path
|
||||
follow_redirect!
|
||||
assert_match "logged out", response.body
|
||||
end
|
||||
|
||||
test "protected admin page redirects to login with an alert" do
|
||||
get admin_dashboard_path
|
||||
assert_redirected_to admin_login_path
|
||||
follow_redirect!
|
||||
assert_match "Please log in to continue", response.body
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user