mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 19:55:16 +00:00
admin api rework (#777)
Co-authored-by: TheUnknownHacker <128781393+The-UnknownHacker@users.noreply.github.com>
This commit is contained in:
parent
a031775e64
commit
063403e4a0
8 changed files with 625 additions and 10 deletions
|
|
@ -19,11 +19,9 @@ module Api
|
|||
true
|
||||
else
|
||||
@admin_api_key.revoke!
|
||||
render json: { error: "lmao no perms" }, status: :unauthorized
|
||||
false
|
||||
end
|
||||
else
|
||||
render json: { error: "lmao no perms" }, status: :unauthorized
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
@ -44,6 +42,12 @@ module Api
|
|||
def render_forbidden
|
||||
render json: { error: "lmao no perms" }, status: :forbidden
|
||||
end
|
||||
|
||||
def require_superadmin
|
||||
unless current_user&.admin_level_superadmin?
|
||||
render json: { error: "lmao no perms" }, status: :unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
76
app/controllers/api/admin/v1/admin_api_keys_controller.rb
Normal file
76
app/controllers/api/admin/v1/admin_api_keys_controller.rb
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
module Api
|
||||
module Admin
|
||||
module V1
|
||||
class AdminApiKeysController < Api::Admin::V1::ApplicationController
|
||||
before_action :set_admin_api_key, only: [ :show, :destroy ]
|
||||
|
||||
def index
|
||||
api_keys = AdminApiKey.includes(:user).active.order(created_at: :desc)
|
||||
|
||||
render json: {
|
||||
admin_api_keys: api_keys.map { |key| admin_api_key_json(key) }
|
||||
}
|
||||
end
|
||||
|
||||
def show
|
||||
render json: admin_api_key_json(@admin_api_key)
|
||||
end
|
||||
|
||||
def create
|
||||
admin_api_key = current_user.admin_api_keys.build(name: params[:name])
|
||||
|
||||
if admin_api_key.save
|
||||
render json: {
|
||||
success: true,
|
||||
message: "Admin API key created successfully",
|
||||
admin_api_key: {
|
||||
id: admin_api_key.id,
|
||||
name: admin_api_key.name,
|
||||
token: admin_api_key.token,
|
||||
created_at: admin_api_key.created_at
|
||||
}
|
||||
}, status: :created
|
||||
else
|
||||
render json: {
|
||||
error: "Failed to create admin API key",
|
||||
errors: admin_api_key.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@admin_api_key.revoke!
|
||||
render json: {
|
||||
success: true,
|
||||
message: "Admin API key has been revoked"
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_admin_api_key
|
||||
@admin_api_key = AdminApiKey.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Admin API key not found" }, status: :not_found
|
||||
end
|
||||
|
||||
def admin_api_key_json(key)
|
||||
{
|
||||
id: key.id,
|
||||
name: key.name,
|
||||
token_preview: "#{key.token[0..10]}...",
|
||||
user: {
|
||||
id: key.user.id,
|
||||
username: key.user.username,
|
||||
display_name: key.user.display_name,
|
||||
admin_level: key.user.admin_level
|
||||
},
|
||||
created_at: key.created_at,
|
||||
revoked_at: key.revoked_at,
|
||||
active: key.active?
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
75
app/controllers/api/admin/v1/deletion_requests_controller.rb
Normal file
75
app/controllers/api/admin/v1/deletion_requests_controller.rb
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
module Api
|
||||
module Admin
|
||||
module V1
|
||||
class DeletionRequestsController < Api::Admin::V1::ApplicationController
|
||||
before_action :require_superadmin
|
||||
before_action :set_deletion_request, only: [ :show, :approve, :reject ]
|
||||
|
||||
def index
|
||||
pending = DeletionRequest.pending.includes(:user).order(requested_at: :asc)
|
||||
approved = DeletionRequest.approved.includes(:user, :admin_approved_by).order(scheduled_deletion_at: :asc)
|
||||
done = DeletionRequest.completed.includes(:user, :admin_approved_by).order(completed_at: :desc).limit(25)
|
||||
|
||||
render json: {
|
||||
pending: pending.map { |dr| deletion_request_json(dr) },
|
||||
approved: approved.map { |dr| deletion_request_json(dr) },
|
||||
completed: done.map { |dr| deletion_request_json(dr) }
|
||||
}
|
||||
end
|
||||
|
||||
def show
|
||||
render json: deletion_request_json(@deletion_request)
|
||||
end
|
||||
|
||||
def approve
|
||||
@deletion_request.approve!(current_user)
|
||||
render json: {
|
||||
success: true,
|
||||
message: "Deletion request approved. Scheduled for #{@deletion_request.scheduled_deletion_at.strftime('%B %d, %Y')}",
|
||||
deletion_request: deletion_request_json(@deletion_request)
|
||||
}
|
||||
end
|
||||
|
||||
def reject
|
||||
@deletion_request.cancel!
|
||||
render json: {
|
||||
success: true,
|
||||
message: "Deletion request rejected",
|
||||
deletion_request: deletion_request_json(@deletion_request)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_deletion_request
|
||||
@deletion_request = DeletionRequest.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Deletion request not found" }, status: :not_found
|
||||
end
|
||||
|
||||
def deletion_request_json(dr)
|
||||
{
|
||||
id: dr.id,
|
||||
user_id: dr.user_id,
|
||||
user: dr.user ? {
|
||||
id: dr.user.id,
|
||||
username: dr.user.username,
|
||||
display_name: dr.user.display_name
|
||||
} : nil,
|
||||
status: dr.status,
|
||||
requested_at: dr.requested_at,
|
||||
scheduled_deletion_at: dr.scheduled_deletion_at,
|
||||
completed_at: dr.completed_at,
|
||||
admin_approved_by: dr.admin_approved_by ? {
|
||||
id: dr.admin_approved_by.id,
|
||||
username: dr.admin_approved_by.username,
|
||||
display_name: dr.admin_approved_by.display_name
|
||||
} : nil,
|
||||
created_at: dr.created_at,
|
||||
updated_at: dr.updated_at
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
68
app/controllers/api/admin/v1/permissions_controller.rb
Normal file
68
app/controllers/api/admin/v1/permissions_controller.rb
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
module Api
|
||||
module Admin
|
||||
module V1
|
||||
class PermissionsController < Api::Admin::V1::ApplicationController
|
||||
before_action :require_superadmin
|
||||
|
||||
def index
|
||||
users = User.where.not(admin_level: :default)
|
||||
.order(admin_level: :asc, username: :asc)
|
||||
|
||||
if params[:search].present?
|
||||
user_ids = User.search_identity(params[:search]).pluck(:id)
|
||||
users = users.where(id: user_ids)
|
||||
end
|
||||
|
||||
users = users.includes(:email_addresses)
|
||||
|
||||
render json: {
|
||||
users: users.map do |user|
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
display_name: user.display_name,
|
||||
slack_username: user.slack_username,
|
||||
github_username: user.github_username,
|
||||
admin_level: user.admin_level,
|
||||
email_addresses: user.email_addresses.map(&:email),
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def update
|
||||
user = User.find(params[:id])
|
||||
previous_level = user.admin_level
|
||||
new_level = params[:admin_level]
|
||||
|
||||
unless User.admin_levels.key?(new_level)
|
||||
return render json: { error: "Invalid admin level" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if user.set_admin_level(new_level)
|
||||
Rails.logger.info "Admin level changed: User #{user.id} (#{user.display_name}) from #{previous_level} to #{new_level} by #{current_user.display_name}"
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
message: "#{user.display_name}'s admin level updated from #{previous_level} to #{new_level}",
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
display_name: user.display_name,
|
||||
admin_level: user.admin_level,
|
||||
previous_admin_level: previous_level,
|
||||
updated_at: user.updated_at
|
||||
}
|
||||
}
|
||||
else
|
||||
render json: { error: "Failed to update admin level" }, status: :unprocessable_entity
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "User not found" }, status: :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
237
app/controllers/api/admin/v1/timeline_controller.rb
Normal file
237
app/controllers/api/admin/v1/timeline_controller.rb
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
module Api
|
||||
module Admin
|
||||
module V1
|
||||
class TimelineController < Api::Admin::V1::ApplicationController
|
||||
MAX_TIMELINE_USERS = 20
|
||||
|
||||
def show
|
||||
timeout_duration = 10.minutes.to_i
|
||||
date = params[:date] ? Date.parse(params[:date]) : Time.current.to_date
|
||||
|
||||
# User selection logic
|
||||
raw_user_ids = params[:user_ids].present? ? params[:user_ids].split(",").map(&:to_i).uniq : []
|
||||
|
||||
# Handle slack_uids parameter
|
||||
if params[:slack_uids].present?
|
||||
slack_uids = params[:slack_uids].split(",").first(MAX_TIMELINE_USERS)
|
||||
users_from_slack_uids = User.where(slack_uid: slack_uids)
|
||||
raw_user_ids += users_from_slack_uids.pluck(:id)
|
||||
end
|
||||
|
||||
# Limit total users to prevent DoS
|
||||
raw_user_ids = raw_user_ids.first(MAX_TIMELINE_USERS)
|
||||
|
||||
# Include current user (admin) by default
|
||||
selected_user_ids = [ current_user.id ] + raw_user_ids
|
||||
selected_user_ids.uniq!
|
||||
|
||||
# Fetch all valid users
|
||||
users_by_id = User.where(id: selected_user_ids).index_by(&:id)
|
||||
valid_user_ids = users_by_id.keys
|
||||
|
||||
mappings_by_user_project = ProjectRepoMapping.where(user_id: valid_user_ids)
|
||||
.group_by(&:user_id)
|
||||
.transform_values { |mappings| mappings.index_by(&:project_name) }
|
||||
|
||||
users_to_process = valid_user_ids.map { |id| users_by_id[id] }.compact
|
||||
|
||||
# Get heartbeats with expanded time range for timezone differences
|
||||
server_start_of_day = date.beginning_of_day.to_f
|
||||
server_end_of_day = date.end_of_day.to_f
|
||||
expanded_start = server_start_of_day - 24.hours.to_i
|
||||
expanded_end = server_end_of_day + 24.hours.to_i
|
||||
|
||||
all_heartbeats = Heartbeat
|
||||
.where(user_id: valid_user_ids, deleted_at: nil)
|
||||
.where("time >= ? AND time <= ?", expanded_start, expanded_end)
|
||||
.select(:id, :user_id, :time, :entity, :project, :editor, :language)
|
||||
.order(:user_id, :time)
|
||||
.to_a
|
||||
|
||||
heartbeats_by_user_id = all_heartbeats.group_by(&:user_id)
|
||||
|
||||
users_with_timeline_data = []
|
||||
|
||||
users_to_process.each do |user|
|
||||
user_tz = user.timezone || "UTC"
|
||||
user_start_of_day = date.in_time_zone(user_tz).beginning_of_day.to_f
|
||||
user_end_of_day = date.in_time_zone(user_tz).end_of_day.to_f
|
||||
|
||||
user_daily_heartbeats_relation = Heartbeat.where(user_id: user.id, deleted_at: nil)
|
||||
.where("time >= ? AND time <= ?", user_start_of_day, user_end_of_day)
|
||||
total_coded_time_seconds = user_daily_heartbeats_relation.duration_seconds
|
||||
|
||||
all_user_heartbeats = heartbeats_by_user_id[user.id] || []
|
||||
user_heartbeats_for_spans = all_user_heartbeats.select { |hb| hb.time >= user_start_of_day && hb.time <= user_end_of_day }
|
||||
calculated_spans_with_details = []
|
||||
|
||||
if user_heartbeats_for_spans.any?
|
||||
current_span_heartbeats = []
|
||||
user_heartbeats_for_spans.each_with_index do |heartbeat, index|
|
||||
current_span_heartbeats << heartbeat
|
||||
is_last_heartbeat = (index == user_heartbeats_for_spans.length - 1)
|
||||
time_to_next = is_last_heartbeat ? Float::INFINITY : (user_heartbeats_for_spans[index + 1].time - heartbeat.time)
|
||||
|
||||
if time_to_next > timeout_duration || is_last_heartbeat
|
||||
if current_span_heartbeats.any?
|
||||
start_time_numeric = current_span_heartbeats.first.time
|
||||
last_hb_time_numeric = current_span_heartbeats.last.time
|
||||
span_duration = last_hb_time_numeric - start_time_numeric
|
||||
span_duration = 0 if span_duration < 0
|
||||
|
||||
files = current_span_heartbeats.map { |h| h.entity&.split("/")&.last }.compact.uniq
|
||||
projects_edited_details_for_span = []
|
||||
unique_project_names_in_current_span = current_span_heartbeats.map(&:project).compact.reject(&:blank?).uniq
|
||||
|
||||
unique_project_names_in_current_span.each do |p_name|
|
||||
repo_mapping = mappings_by_user_project.dig(user.id, p_name)
|
||||
projects_edited_details_for_span << {
|
||||
name: p_name,
|
||||
repo_url: repo_mapping&.repo_url
|
||||
}
|
||||
end
|
||||
|
||||
editors = current_span_heartbeats.map(&:editor).compact.uniq
|
||||
languages = current_span_heartbeats.map(&:language).compact.uniq
|
||||
|
||||
calculated_spans_with_details << {
|
||||
start_time: start_time_numeric,
|
||||
end_time: last_hb_time_numeric,
|
||||
duration: span_duration,
|
||||
files_edited: files,
|
||||
projects_edited_details: projects_edited_details_for_span,
|
||||
editors: editors,
|
||||
languages: languages
|
||||
}
|
||||
current_span_heartbeats = []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
users_with_timeline_data << {
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
display_name: user.display_name,
|
||||
slack_username: user.slack_username,
|
||||
github_username: user.github_username,
|
||||
timezone: user.timezone,
|
||||
avatar_url: user.avatar_url
|
||||
},
|
||||
spans: calculated_spans_with_details,
|
||||
total_coded_time: total_coded_time_seconds
|
||||
}
|
||||
end
|
||||
|
||||
# Get commit markers
|
||||
commits_for_timeline = Commit.where(
|
||||
user_id: selected_user_ids,
|
||||
created_at: date.beginning_of_day..date.end_of_day
|
||||
)
|
||||
|
||||
timeline_commit_markers = commits_for_timeline.map do |commit|
|
||||
raw = commit.github_raw || {}
|
||||
commit_time = if raw.dig("commit", "committer", "date")
|
||||
Time.parse(raw["commit"]["committer"]["date"])
|
||||
else
|
||||
commit.created_at
|
||||
end
|
||||
{
|
||||
user_id: commit.user_id,
|
||||
timestamp: commit_time.to_f,
|
||||
additions: (raw["stats"] && raw["stats"]["additions"]) || raw.dig("files", 0, "additions"),
|
||||
deletions: (raw["stats"] && raw["stats"]["deletions"]) || raw.dig("files", 0, "deletions"),
|
||||
github_url: raw["html_url"]
|
||||
}
|
||||
end
|
||||
|
||||
render json: {
|
||||
date: date.iso8601,
|
||||
next_date: (date + 1.day).iso8601,
|
||||
prev_date: (date - 1.day).iso8601,
|
||||
users: users_with_timeline_data,
|
||||
commit_markers: timeline_commit_markers
|
||||
}
|
||||
rescue Date::Error
|
||||
render json: { error: "Invalid date format" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def search_users
|
||||
query_term = params[:query].to_s.downcase
|
||||
|
||||
if query_term.blank?
|
||||
return render json: { error: "Query parameter is required" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
user_id_match = nil
|
||||
if query_term.match?(/^\d+$/)
|
||||
user_id = query_term.to_i
|
||||
user_id_match = User.where(id: user_id).first
|
||||
end
|
||||
|
||||
if user_id_match
|
||||
results = [ {
|
||||
id: user_id_match.id,
|
||||
display_name: user_id_match.display_name,
|
||||
avatar_url: user_id_match.avatar_url
|
||||
} ]
|
||||
else
|
||||
users = User.where("LOWER(username) LIKE :query OR LOWER(slack_username) LIKE :query OR CAST(id AS TEXT) LIKE :query OR EXISTS (SELECT 1 FROM email_addresses WHERE email_addresses.user_id = users.id AND LOWER(email_addresses.email) LIKE :query)", query: "%#{query_term}%")
|
||||
.order(Arel.sql("CASE WHEN LOWER(username) = #{ActiveRecord::Base.connection.quote(query_term)} THEN 0 ELSE 1 END, username ASC"))
|
||||
.limit(20)
|
||||
.select(:id, :username, :slack_username, :github_username, :slack_avatar_url, :github_avatar_url)
|
||||
|
||||
results = users.map do |user|
|
||||
{
|
||||
id: user.id,
|
||||
display_name: user.display_name,
|
||||
avatar_url: user.avatar_url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
render json: { users: results }
|
||||
end
|
||||
|
||||
def leaderboard_users
|
||||
period = params[:period]
|
||||
limit = 25
|
||||
|
||||
leaderboard_period_type = (period == "last_7_days") ? :last_7_days : :daily
|
||||
start_date = Date.current
|
||||
|
||||
leaderboard = Leaderboard.where.not(finished_generating_at: nil)
|
||||
.find_by(start_date: start_date, period_type: leaderboard_period_type, deleted_at: nil)
|
||||
|
||||
user_ids_from_leaderboard = leaderboard ? leaderboard.entries.order(total_seconds: :desc).limit(limit).pluck(:user_id) : []
|
||||
|
||||
all_ids_to_fetch = user_ids_from_leaderboard.dup
|
||||
all_ids_to_fetch.unshift(current_user.id).uniq!
|
||||
|
||||
users_data = User.where(id: all_ids_to_fetch)
|
||||
.select(:id, :username, :slack_username, :github_username, :slack_avatar_url, :github_avatar_url)
|
||||
.index_by(&:id)
|
||||
|
||||
final_user_objects = []
|
||||
# Add admin first
|
||||
if admin_data = users_data[current_user.id]
|
||||
final_user_objects << { id: admin_data.id, display_name: admin_data.display_name, avatar_url: admin_data.avatar_url }
|
||||
end
|
||||
|
||||
# Add leaderboard users
|
||||
user_ids_from_leaderboard.each do |uid|
|
||||
break if final_user_objects.size >= limit
|
||||
next if uid == current_user.id
|
||||
|
||||
if user_data = users_data[uid]
|
||||
final_user_objects << { id: user_data.id, display_name: user_data.display_name, avatar_url: user_data.avatar_url }
|
||||
end
|
||||
end
|
||||
|
||||
render json: { users: final_user_objects }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
module Api
|
||||
module Admin
|
||||
module V1
|
||||
class TrustLevelAuditLogsController < Api::Admin::V1::ApplicationController
|
||||
def index
|
||||
audit_logs = TrustLevelAuditLog.includes(:user, :changed_by)
|
||||
.recent
|
||||
.limit(250)
|
||||
|
||||
# Filter by user_id
|
||||
if params[:user_id].present?
|
||||
user = User.find_by(id: params[:user_id])
|
||||
if user
|
||||
audit_logs = audit_logs.for_user(user)
|
||||
else
|
||||
return render json: { error: "User not found" }, status: :not_found
|
||||
end
|
||||
end
|
||||
|
||||
# Filter by admin_id (changed_by)
|
||||
if params[:admin_id].present?
|
||||
admin = User.find_by(id: params[:admin_id])
|
||||
if admin
|
||||
audit_logs = audit_logs.by_admin(admin)
|
||||
else
|
||||
return render json: { error: "Admin not found" }, status: :not_found
|
||||
end
|
||||
end
|
||||
|
||||
# Search by user
|
||||
if params[:user_search].present?
|
||||
user_ids = User.search_identity(params[:user_search]).pluck(:id)
|
||||
audit_logs = audit_logs.where(user_id: user_ids)
|
||||
end
|
||||
|
||||
# Search by admin
|
||||
if params[:admin_search].present?
|
||||
admin_ids = User.search_identity(params[:admin_search]).pluck(:id)
|
||||
audit_logs = audit_logs.where(changed_by_id: admin_ids)
|
||||
end
|
||||
|
||||
# Filter by trust level
|
||||
if params[:trust_level_filter].present? && params[:trust_level_filter] != "all"
|
||||
case params[:trust_level_filter]
|
||||
when "to_convicted"
|
||||
audit_logs = audit_logs.where(new_trust_level: "red")
|
||||
when "to_trusted"
|
||||
audit_logs = audit_logs.where(new_trust_level: "green")
|
||||
when "to_suspected"
|
||||
audit_logs = audit_logs.where(new_trust_level: "yellow")
|
||||
when "to_unscored"
|
||||
audit_logs = audit_logs.where(new_trust_level: "blue")
|
||||
end
|
||||
end
|
||||
|
||||
render json: {
|
||||
audit_logs: audit_logs.map do |log|
|
||||
{
|
||||
id: log.id,
|
||||
user: {
|
||||
id: log.user.id,
|
||||
username: log.user.username,
|
||||
display_name: log.user.display_name
|
||||
},
|
||||
previous_trust_level: log.previous_trust_level,
|
||||
new_trust_level: log.new_trust_level,
|
||||
changed_by: {
|
||||
id: log.changed_by.id,
|
||||
username: log.changed_by.username,
|
||||
display_name: log.changed_by.display_name,
|
||||
admin_level: log.changed_by.admin_level
|
||||
},
|
||||
reason: log.reason,
|
||||
notes: log.notes,
|
||||
created_at: log.created_at
|
||||
}
|
||||
end,
|
||||
total_count: audit_logs.count
|
||||
}
|
||||
end
|
||||
|
||||
def show
|
||||
audit_log = TrustLevelAuditLog.find(params[:id])
|
||||
|
||||
render json: {
|
||||
id: audit_log.id,
|
||||
user: {
|
||||
id: audit_log.user.id,
|
||||
username: audit_log.user.username,
|
||||
display_name: audit_log.user.display_name,
|
||||
current_trust_level: audit_log.user.trust_level
|
||||
},
|
||||
previous_trust_level: audit_log.previous_trust_level,
|
||||
new_trust_level: audit_log.new_trust_level,
|
||||
changed_by: {
|
||||
id: audit_log.changed_by.id,
|
||||
username: audit_log.changed_by.username,
|
||||
display_name: audit_log.changed_by.display_name,
|
||||
admin_level: audit_log.changed_by.admin_level
|
||||
},
|
||||
reason: audit_log.reason,
|
||||
notes: audit_log.notes,
|
||||
created_at: audit_log.created_at,
|
||||
updated_at: audit_log.updated_at
|
||||
}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Audit log not found" }, status: :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -116,6 +116,24 @@ class User < ApplicationRecord
|
|||
|
||||
has_many :wakatime_mirrors, dependent: :destroy
|
||||
|
||||
scope :search_identity, ->(term) {
|
||||
term = term.to_s.strip.downcase
|
||||
return none if term.blank?
|
||||
|
||||
pattern = "%#{sanitize_sql_like(term)}%"
|
||||
|
||||
left_joins(:email_addresses)
|
||||
.where(
|
||||
"LOWER(users.username) LIKE :p OR " \
|
||||
"LOWER(users.slack_username) LIKE :p OR " \
|
||||
"LOWER(users.github_username) LIKE :p OR " \
|
||||
"LOWER(email_addresses.email) LIKE :p OR " \
|
||||
"CAST(users.id AS TEXT) LIKE :p",
|
||||
p: pattern
|
||||
)
|
||||
.distinct
|
||||
}
|
||||
|
||||
has_many :trust_level_audit_logs, dependent: :destroy
|
||||
has_many :trust_level_changes_made, class_name: "TrustLevelAuditLog", foreign_key: "changed_by_id", dependent: :destroy
|
||||
has_many :deletion_requests, dependent: :destroy
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ Rails.application.routes.draw do
|
|||
end
|
||||
get "/impersonate/:id", to: "sessions#impersonate", as: :impersonate_user
|
||||
end
|
||||
|
||||
constraints AdminLevelConstraint.new(:superadmin) do
|
||||
namespace :admin do
|
||||
resources :permissions, only: [ :index, :update ]
|
||||
end
|
||||
end
|
||||
get "/stop_impersonating", to: "sessions#stop_impersonating", as: :stop_impersonating
|
||||
|
||||
if Rails.env.development?
|
||||
|
|
@ -120,12 +126,7 @@ Rails.application.routes.draw do
|
|||
post "my/settings/rotate_api_key", to: "users#rotate_api_key", as: :my_settings_rotate_api_key
|
||||
|
||||
namespace :my do
|
||||
resources :project_repo_mappings, param: :project_name, only: [ :edit, :update ], constraints: { project_name: /.+/ } do
|
||||
member do
|
||||
patch :archive
|
||||
patch :unarchive
|
||||
end
|
||||
end
|
||||
resources :project_repo_mappings, param: :project_name, only: [ :edit, :update ], constraints: { project_name: /.+/ }
|
||||
# resource :mailing_address, only: [ :show, :edit ]
|
||||
# get "mailroom", to: "mailroom#index"
|
||||
resources :heartbeats, only: [] do
|
||||
|
|
@ -136,8 +137,6 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
get "deletion", to: "deletion_requests#show", as: :deletion
|
||||
post "deletion", to: "deletion_requests#create", as: :create_deletion
|
||||
delete "deletion", to: "deletion_requests#cancel", as: :cancel_deletion
|
||||
|
|
@ -207,6 +206,32 @@ Rails.application.routes.draw do
|
|||
post "user/get_user_by_email", to: "admin#get_user_by_email"
|
||||
post "user/search_fuzzy", to: "admin#search_users_fuzzy"
|
||||
post "user/convict", to: "admin#user_convict"
|
||||
|
||||
# Admin API Keys management
|
||||
resources :admin_api_keys, only: [ :index, :show, :create, :destroy ]
|
||||
|
||||
# Trust level audit logs
|
||||
resources :trust_level_audit_logs, only: [ :index, :show ]
|
||||
|
||||
# Deletion requests
|
||||
resources :deletion_requests, only: [ :index, :show ] do
|
||||
member do
|
||||
post :approve
|
||||
post :reject
|
||||
end
|
||||
end
|
||||
|
||||
# Permissions management
|
||||
resources :permissions, only: [ :index ] do
|
||||
collection do
|
||||
patch ":id", to: "permissions#update", as: :update
|
||||
end
|
||||
end
|
||||
|
||||
# Timeline
|
||||
get "timeline", to: "timeline#show"
|
||||
get "timeline/search_users", to: "timeline#search_users"
|
||||
get "timeline/leaderboard_users", to: "timeline#leaderboard_users"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue