Skip to content

Commit

Permalink
feat: Adding member endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
martintomas committed Dec 25, 2024
1 parent 6a4ea80 commit ba6d604
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 207 deletions.
36 changes: 36 additions & 0 deletions backend/app/controllers/api/v1/members_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module API
module V1
class MembersController < BaseController
load_and_authorize_resource except: :sign_in

def me
render json: API::V1::MemberSerializer.new(current_member).serializable_hash
end

def update
if @member.update(member_params)
render json: API::V1::MemberSerializer.new(@member).serializable_hash
else
raise API::UnprocessableEntityError, @member.errors.full_messages.to_sentence
end
end

def sign_in
member = Member.find_by_email params[:email]

if member&.valid_password?(params[:password])
render json: {token: JWTAuth.encode(member)}
else
raise API::UnprocessableEntityError, I18n.t("devise.failure.invalid", authentication_keys: :email)
end
end

private

def member_params
p = params.permit :first_name, :last_name, :email, :password, :password_confirmation
p[:password].blank? ? p.except(:password, :password_confirmation) : p
end
end
end
end
15 changes: 0 additions & 15 deletions backend/app/controllers/api/v1/token_controller.rb

This file was deleted.

3 changes: 2 additions & 1 deletion backend/app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def initialize(member_or_admin)
if member_or_admin.is_a?(Admin)
@admin = member_or_admin
admin_rights
else
elsif member_or_admin.is_a?(Member)
@member = member_or_admin
member_rights
end
Expand All @@ -28,6 +28,7 @@ def admin_rights
end

def member_rights
can %i[me update], Member, id: @member.id
end

def default_rights
Expand Down
2 changes: 1 addition & 1 deletion backend/app/models/member.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Member < ApplicationRecord

validates_presence_of :first_name, :last_name

validates :password, length: {minimum: 12, message: :password_length}
validates :password, length: {minimum: 12, message: :password_length}, allow_nil: true
validate :password_complexity

def full_name
Expand Down
7 changes: 7 additions & 0 deletions backend/app/serializers/api/v1/member_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module API
module V1
class MemberSerializer < BaseSerializer
attributes :id, :email, :first_name, :last_name, :created_at, :updated_at
end
end
end
3 changes: 3 additions & 0 deletions backend/app/services/jwt_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class JWTAuth
def self.encode(member)
payload = {
member_id: member.id,
first_name: member.first_name,
last_name: member.last_name,
email: member.email,
exp: 1.week.from_now.to_i
}
JWT.encode payload, SECRET, ALGORITHM
Expand Down
7 changes: 6 additions & 1 deletion backend/config/routes/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
get :download
end
end
resources :token, only: %i[create]
resources :members, only: %i[update] do
collection do
get :me
post :sign_in
end
end
resource :reset_password, only: [:create, :update]
end
end
14 changes: 14 additions & 0 deletions backend/spec/fixtures/snapshots/api/v1/get-member-me.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"data": {
"id": "f1908e5b-0281-40c1-a045-40451156a8b3",
"type": "member",
"attributes": {
"id": "f1908e5b-0281-40c1-a045-40451156a8b3",
"email": "[email protected]",
"first_name": "Dawna",
"last_name": "Block",
"created_at": "2024-12-25T15:24:15.424Z",
"updated_at": "2024-12-25T15:24:15.424Z"
}
}
}
14 changes: 14 additions & 0 deletions backend/spec/fixtures/snapshots/api/v1/update-member.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"data": {
"id": "8e5c4b29-97f5-4379-b5ce-15cf730d991e",
"type": "member",
"attributes": {
"id": "8e5c4b29-97f5-4379-b5ce-15cf730d991e",
"email": "[email protected]",
"first_name": "New",
"last_name": "Name",
"created_at": "2024-12-25T15:40:17.982Z",
"updated_at": "2024-12-25T15:40:17.990Z"
}
}
}
153 changes: 153 additions & 0 deletions backend/spec/requests/api/v1/members_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# This file was generated with rails g enum WidgetSlug

require "swagger_helper"

RSpec.describe "API V1 Members", type: :request do
path "/api/v1/members/{id}" do
put "Updates a member" do
tags "Member"
consumes "application/json"
produces "application/json"
security [Bearer: {}]

include_context "with authorization"

parameter name: :id, in: :path, type: :string
parameter name: :member_params, in: :body, schema: {
type: :object,
properties: {
first_name: {type: :string, nullable: true},
last_name: {type: :string, nullable: true},
email: {type: :string, nullable: true},
password: {type: :string, nullable: true},
password_confirmation: {type: :string, nullable: true}
}
}

let(:member) { create :member }
let(:id) { member.id }
let(:member_params) { {} }

response "200", :success do
schema type: :object, properties: {data: {"$ref" => "#/components/schemas/member"}}

let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" }
let(:member_params) do
{
first_name: "New",
last_name: "Name",
email: "[email protected]"
}
end

run_test!

it "matches snapshot", generate_swagger_example: true do
expect(response.body).to match_snapshot("api/v1/update-member")
end

context "when updating password" do
let!(:old_password) { member.encrypted_password }
let(:member_params) do
{
password: "NewPassword123456",
password_confirmation: "NewPassword123456"
}
end

it "returns 200" do
expect(response).to have_http_status(:ok)
expect(member.reload.encrypted_password).not_to eq(old_password)
end
end
end

response "422", "Invalid attributes" do
schema "$ref" => "#/components/schemas/errors"

let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" }
let(:member_params) do
{
email: "invalid-email"
}
end

run_test!
end
end
end

path "/api/v1/members/me" do
get "Returns current member" do
tags "Member"
consumes "application/json"
produces "application/json"
security [Bearer: {}]

include_context "with authorization"

response "200", :success do
schema type: :object, properties: {data: {"$ref" => "#/components/schemas/member"}}

let(:member) { create :member }
let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" }

run_test!

it "matches snapshot", generate_swagger_example: true do
expect(response.body).to match_snapshot("api/v1/get-member-me")
end
end
end
end

path "/api/v1/members/sign_in" do
post "Returns member access token" do
tags "Member"
consumes "application/json"
produces "application/json"
parameter name: :member_params, in: :body, schema: {
type: :object,
properties: {
email: {type: :string},
password: {type: :string}
},
required: ["email", "password"]
}

response "200", :success do
schema type: :object, properties: {
token: {type: :string}
}

let(:member) { create :member, password: password }
let(:password) { "SuperSecret6" }
let(:member_params) do
{
email: member.email,
password: password
}
end

run_test!

it "returns token", generate_swagger_example: true do
expect(response_json["token"]).not_to be_nil
end
end

response "422", "Invalid credentials" do
schema "$ref" => "#/components/schemas/errors"

let(:member_params) do
{
email: "[email protected]",
password: "wrongpassword"
}
end

run_test!
end
end
end
end
55 changes: 0 additions & 55 deletions backend/spec/requests/api/v1/token_spec.rb

This file was deleted.

11 changes: 11 additions & 0 deletions backend/spec/support/shared_examples/api/authorization_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "swagger_helper"

RSpec.shared_examples "with authorization" do
response "403", "Forbidden", generate_swagger_example: true do
let(:Authorization) { nil }

schema "$ref" => "#/components/schemas/errors"

run_test!
end
end
Loading

0 comments on commit ba6d604

Please sign in to comment.