create basic backend with login
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
class Api::RegistrationsController < ApplicationController
|
||||
skip_before_action :authenticate_request!, only: [:create]
|
||||
|
||||
def create
|
||||
user = User.new(registration_params)
|
||||
|
||||
if user.save
|
||||
token = JwtService.encode(user_id: user.id)
|
||||
render json: {
|
||||
user: user_response(user),
|
||||
token: token
|
||||
}, status: :created
|
||||
else
|
||||
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def registration_params
|
||||
params.require(:user).permit(:nickname, :password, :avatar)
|
||||
end
|
||||
|
||||
def user_response(user)
|
||||
{
|
||||
id: user.id,
|
||||
nickname: user.nickname,
|
||||
avatar_url: user.avatar.attached? ? url_for(user.avatar) : nil,
|
||||
created_at: user.created_at
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
class Api::SessionsController < ApplicationController
|
||||
skip_before_action :authenticate_request!, only: [:create]
|
||||
|
||||
def create
|
||||
user = User.authenticate_by(params.permit(:nickname, :password))
|
||||
|
||||
if user
|
||||
token = JwtService.encode(user_id: user.id)
|
||||
render json: {
|
||||
user: user_response(user),
|
||||
token: token
|
||||
}
|
||||
else
|
||||
render json: { error: "Invalid nickname or password" }, status: :unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
# JWT is stateless; client should discard the token.
|
||||
# For server-side revocation, implement a token blacklist here.
|
||||
render json: { message: "Signed out successfully" }
|
||||
end
|
||||
|
||||
def me
|
||||
render json: { user: user_response(current_user) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_response(user)
|
||||
{
|
||||
id: user.id,
|
||||
nickname: user.nickname,
|
||||
avatar_url: user.avatar.attached? ? url_for(user.avatar) : nil,
|
||||
created_at: user.created_at
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
class ApplicationController < ActionController::API
|
||||
include ActionController::HttpAuthentication::Token::ControllerMethods
|
||||
|
||||
before_action :authenticate_request!
|
||||
|
||||
attr_reader :current_user
|
||||
|
||||
private
|
||||
|
||||
def authenticate_request!
|
||||
token = extract_token
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized unless token
|
||||
|
||||
decoded = JwtService.decode(token)
|
||||
@current_user = User.find_by(id: decoded[:user_id]) if decoded
|
||||
render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user
|
||||
end
|
||||
|
||||
def extract_token
|
||||
auth_header = request.headers["Authorization"]
|
||||
return auth_header.split(" ").last if auth_header&.start_with?("Bearer ")
|
||||
|
||||
params[:token]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
primary_abstract_class
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
class User < ApplicationRecord
|
||||
has_secure_password validations: false
|
||||
|
||||
has_many :sessions, dependent: :destroy
|
||||
has_one_attached :avatar
|
||||
|
||||
normalizes :nickname, with: ->(n) { n.strip }
|
||||
|
||||
validates :nickname, presence: true, uniqueness: { case_sensitive: false }, length: { minimum: 3, maximum: 24 }
|
||||
validates :password, presence: true, length: { minimum: 6 }, if: -> { new_record? || password_digest_changed? }
|
||||
validate :avatar_content_type, if: -> { avatar.attached? }
|
||||
|
||||
private
|
||||
def avatar_content_type
|
||||
unless avatar.blob.content_type.in?(%w[image/png image/jpeg image/gif image/webp])
|
||||
errors.add(:avatar, "must be a PNG, JPEG, GIF or WebP image")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
class JwtService
|
||||
SECRET_KEY = Rails.application.secret_key_base
|
||||
ALGORITHM = "HS256"
|
||||
EXPIRATION = 7.days
|
||||
|
||||
def self.encode(payload)
|
||||
payload[:exp] = EXPIRATION.from_now.to_i
|
||||
JWT.encode(payload, SECRET_KEY, ALGORITHM)
|
||||
end
|
||||
|
||||
def self.decode(token)
|
||||
decoded = JWT.decode(token, SECRET_KEY, true, { algorithm: ALGORITHM }).first
|
||||
HashWithIndifferentAccess.new(decoded).slice(:user_id, :exp)
|
||||
rescue JWT::DecodeError, JWT::ExpiredSignature
|
||||
nil
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user