module Api
module V2
- class ApiController < ApplicationController
- skip_before_action :verify_authenticity_token
- before_action :authenticate_api_key
+ class ApiController < ApplicationController
+ skip_before_action :verify_authenticity_token
+ before_action :authenticate_api_key
- private
+ private
- def authenticate_api_key
- api_key = request.headers['X-API-Key'] || params[:api_key]
- unless ApiKey.exists?(key: api_key)
- render json: { errors: [{ status: '401', title: 'Invalid API key' }] }, status: :unauthorized
- end
- end
- end
+ def authenticate_api_key
+ api_key = ApiKey.includes(:user).find_by(key: request.headers['X-API-Key'])
+
+ if api_key
+ api_key.track_usage!
+ @current_api_user = api_key.user
+ else
+ render json: { errors: [{ status: '401', title: 'Invalid API key' }] }, status: :unauthorized
+ end
+ end
+
+ def current_api_user
+ @current_api_user
+ end
+ end
end
end
class ApiKeysController < ApplicationController
before_action :set_api_key, only: [:show, :destroy]
- #before_action :authenticate_user!
include AdminAuthenticatable
def index
- @api_keys = ApiKey.all
+ @api_keys = ApiKey.includes(:user).all
end
def show
end
-
+
def new
- @api_key = ApiKey.new
+ @api_key = ApiKey.new
+ @users = User.all
end
def create
- @api_key = ApiKey.new
- if @api_key.save
- redirect_to @api_key, notice: 'API key was successfully created.'
- else
- render :new
- end
+ @api_key = ApiKey.new(api_key_params)
+
+ if @api_key.save
+ redirect_to @api_key, notice: 'API key was successfully created.'
+ else
+ @users = User.all
+ render :new
+ end
end
def destroy
- @api_key.destroy
- redirect_to api_keys_url, notice: 'API key was successfully destroyed.'
+ @api_key.destroy
+ redirect_to api_keys_url, notice: 'API key was successfully destroyed.'
end
private
def set_api_key
- @api_key = ApiKey.find(params[:id])
+ @api_key = ApiKey.includes(:user).find(params[:id])
end
- def authorize_admin
- redirect_to root_path, alert: 'Not authorized.' unless current_user.admin?
+ def api_key_params
+ params.require(:api_key).permit(:user_id, :description)
end
-end
\ No newline at end of file
+end
<div class="container">
-<h1>API Keys</h1>
-
-<%= link_to 'New API Key', new_api_key_path, class: "button" %>
-<%= link_to 'Home', root_path, class: "button" %>
+ <h1>API Keys</h1>
+ <%= link_to 'New API Key', new_api_key_path, class: "button" %>
+ <%= link_to 'Home', root_path, class: "button" %>
</div>
-
<%= render partial: "layouts/alert" %>
-
-
<div class="post">
- <div class="container">
- <table>
- <thead>
- <tr>
- <th>Key</th>
- <th>Created At</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- <% @api_keys.each do |api_key| %>
- <tr>
- <td><%= api_key.key %></td>
- <td><%= api_key.created_at %></td>
- <td>
- <%= link_to 'View', api_key, class: "button" %>
- <%= button_to 'Destroy', api_key, method: :delete %>
- </td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
+ <div class="container">
+ <table>
+ <thead>
+ <tr>
+ <th>Key</th>
+ <th>User</th>
+ <th>Description</th>
+ <th>Last Used</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @api_keys.each do |api_key| %>
+ <tr>
+ <td><code><%= api_key.key %></code></td>
+ <td><%= api_key.user&.email || 'No user assigned' %></td>
+ <td><%= api_key.description.presence || 'No description' %></td>
+ <td><%= api_key.last_used_at ? time_ago_in_words(api_key.last_used_at) + " ago" : "Never used" %></td>
+ <td><%= time_ago_in_words(api_key.created_at) %> ago</td>
+ <td>
+ <%= link_to 'View', api_key, class: "button" %>
+ <%= button_to 'Destroy', api_key, method: :delete, class: "button danger" %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
</div>
<div class="container">
- <h1>Generate New API Key</h1>
- <%= link_to 'Back to API Keys', api_keys_path, class: "button" %>
+ <h1>Generate New API Key</h1>
+ <%= link_to 'Back to API Keys', api_keys_path, class: "button" %>
</div>
-
<%= render partial: "layouts/alert" %>
-
<div class="post">
- <div class="container">
- <p>Click the button below to generate a new API key.</p>
+ <div class="container">
+ <%= form_with(model: @api_key, local: true) do |form| %>
+ <% if @api_key.errors.any? %>
+ <div id="error_explanation">
+ <h2><%= pluralize(@api_key.errors.count, "error") %> prohibited this API key from being saved:</h2>
+ <ul>
+ <% @api_key.errors.full_messages.each do |message| %>
+ <li><%= message %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
- <%= form_with(model: @api_key, local: true) do |form| %>
- <% if @api_key.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@api_key.errors.count, "error") %> prohibited this API key from being saved:</h2>
+ <div class="field">
+ <%= form.label :user_id, "Assign to User (Optional)" %>
+ <%= form.collection_select :user_id, User.all, :id, :email, { include_blank: "No user" } %>
+ </div>
- <ul>
- <% @api_key.errors.full_messages.each do |message| %>
- <li><%= message %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
+ <div class="field">
+ <%= form.label :description, "Description (Optional)" %>
+ <%= form.text_field :description, placeholder: "e.g., API key for mobile app" %>
+ </div>
- <div class="actions">
- <%= form.submit "Generate API Key" %>
- </div>
- <% end %>
- </div>
+ <div class="actions">
+ <%= form.submit "Generate API Key" %>
+ </div>
+ <% end %>
+ </div>
</div>
<div class="container">
- <h1>API Key Details</h1>
- <%= link_to 'Back to API Keys', api_keys_path, class: "button" %>
+ <h1>API Key Details</h1>
+ <%= link_to 'Back to API Keys', api_keys_path, class: "button" %>
</div>
-
<%= render partial: "layouts/alert" %>
-
<div class="post">
- <div class="container">
- <p>
- <strong>Key:</strong>
- <%= @api_key.key %>
- </p>
+ <div class="container">
+ <div class="api-key-details">
+ <h3>Key Information</h3>
+ <p>
+ <strong>API Key:</strong><br>
+ <code><%= @api_key.key %></code>
+ </p>
+
+ <p>
+ <strong>Assigned User:</strong><br>
+ <%= @api_key.user&.email || 'No user assigned' %>
+ </p>
+
+ <p>
+ <strong>Description:</strong><br>
+ <%= @api_key.description.presence || 'No description provided' %>
+ </p>
+
+ <h3>Usage Information</h3>
+ <p>
+ <strong>Created:</strong><br>
+ <%= time_ago_in_words(@api_key.created_at) %> ago
+ (<%= @api_key.created_at.strftime("%B %d, %Y at %I:%M %p") %>)
+ </p>
+
+ <p>
+ <strong>Last Used:</strong><br>
+ <% if @api_key.last_used_at %>
+ <%= time_ago_in_words(@api_key.last_used_at) %> ago
+ (<%= @api_key.last_used_at.strftime("%B %d, %Y at %I:%M %p") %>)
+ <% else %>
+ Never used
+ <% end %>
+ </p>
- <p>
- <strong>Created at:</strong>
- <%= @api_key.created_at %>
- </p>
- </div>
+ <div class="actions" style="margin-top: 2em;">
+ <%= button_to 'Delete API Key', @api_key, method: :delete, data: { confirm: 'Are you sure you want to delete this API key?' }, class: "button danger" %>
+ </div>
+ </div>
+ </div>
</div>
--- /dev/null
+class AssignApiKeysToFirstUser < ActiveRecord::Migration[7.2]
+ def up
+ user = User.find_by(id: 1)
+
+ if user
+ # Store original associations for potential rollback
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS api_keys_backup (
+ id int,
+ user_id int
+ )
+ SQL
+
+ execute <<-SQL
+ INSERT INTO api_keys_backup (id, user_id)
+ SELECT id, user_id FROM api_keys
+ SQL
+
+ # Update all API keys to belong to user 1
+ ApiKey.where(user_id: nil).update_all(user_id: 1)
+
+ puts "Assigned #{ApiKey.where(user_id: 1).count} API keys to user #{user.email}"
+ else
+ puts "Warning: User with ID 1 not found. Migration aborted."
+ raise ActiveRecord::IrreversibleMigration
+ end
+ end
+
+ def down
+ # Restore original associations
+ execute <<-SQL
+ UPDATE api_keys
+ SET user_id = backup.user_id
+ FROM api_keys_backup backup
+ WHERE api_keys.id = backup.id
+ SQL
+
+ execute "DROP TABLE IF EXISTS api_keys_backup"
+ end
+end
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2025_01_09_015845) do
+ActiveRecord::Schema[7.2].define(version: 2025_01_09_031618) do
create_table "api_keys", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "key"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.bigint "user_id"
+ t.datetime "last_used_at"
+ t.string "description"
t.index ["key"], name: "index_api_keys_on_key", unique: true
+ t.index ["user_id"], name: "index_api_keys_on_user_id"
+ end
+
+ create_table "api_keys_backup", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.integer "id"
+ t.integer "user_id"
end
create_table "pages", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
+
+ add_foreign_key "api_keys", "users"
end