Skip to content

Commit

Permalink
Merge pull request #37 from turingschool/feature/private-links
Browse files Browse the repository at this point in the history
Feature/private links
  • Loading branch information
jdmchugh111 authored Sep 12, 2024
2 parents a1ec7cf + a3cc07f commit 0ef11cf
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 10 deletions.
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ This is the backend API repository for TurLink. TurLink is a link shortener app
"short": "tur.link/4a7c204baeacaf2c",
"user_id": 1,
"click_count": 1,
"last_click": "2024-08-28T12:34:56.789Z"
"last_click": "2024-08-28T12:34:56.789Z",
"private": false
}
}
}
Expand Down Expand Up @@ -450,3 +451,47 @@ This is the backend API repository for TurLink. TurLink is a link shortener app
- This endpoint currently returns mock data.
- In the future, it will provide an actual summary of the content at the given link.
- The summary is expected to be a string with numbered points, separated by newline characters.

### Update Link Privacy
- **PATCH** `/api/v1/users/:user_id/links/:id/update_privacy`
- Description: Updates the privacy setting of a specific link for a user.
- Request Parameters:
- `user_id`: The ID of the user who owns the link
- `id`: The ID of the link to update
- Request Body:
```json
{
"private": "true" // or "false" to make the link public
}
```
- Example Request: PATCH `https://turlink-be-53ba7254a7c1.herokuapp.com/api/v1/users/1/links/1/update_privacy`
- Successful Response (200 OK):
```json
{
"message": "Privacy setting updated successfully"
}
```
- Error Responses:
- 403 Forbidden (If the user doesn't own the link):
```json
{
"error": "Unauthorized to update this link"
}
```
- 404 Not Found (If the user or link doesn't exist):
```json
{
"error": "User or Link not found"
}
```
- 422 Unprocessable Entity (If the update fails):
```json
{
"error": "Failed to update privacy setting"
}
```

- Notes:
- Private links are only accessible by their owners.
- Private links are excluded from the top links listing.
- Attempting to access a private link without proper authorization will result in a 404 Not Found error.
22 changes: 20 additions & 2 deletions app/controllers/api/v1/links_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def index

def show
link = Link.find_by(short: params[:short])
if link
if link && (!link.private? || (current_user && link.user_id == current_user.id))
link.increment!(:click_count)
link.update(last_click: Time.current)
render json: LinkSerializer.new(link)
Expand All @@ -27,7 +27,7 @@ def show
end

def top_links
query = Link.order(click_count: :desc).limit(5)
query = Link.where(private: false).order(click_count: :desc).limit(5)

query = query.joins(:tags).where(tags: { name: params[:tag] }) if params[:tag].present?

Expand All @@ -36,6 +36,24 @@ def top_links
render json: LinkSerializer.new(links)
end

def update_privacy
user = User.find(params[:user_id])
link = Link.find(params[:id])
is_private = params[:private] == 'true'

if link.user_id == user.id
if link.update(private: is_private)
render json: { message: 'Privacy setting updated successfully' }, status: :ok
else
render json: { error: 'Failed to update privacy setting' }, status: :unprocessable_entity
end
else
render json: { error: 'Unauthorized to update this link' }, status: :forbidden
end
rescue ActiveRecord::RecordNotFound
render json: { error: 'User or Link not found' }, status: :not_found
end

private

def not_found_error
Expand Down
6 changes: 6 additions & 0 deletions app/models/link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class Link < ApplicationRecord
validates_presence_of :user_id
validates_presence_of :original
validates :short, uniqueness: true, presence: true
validates :private, inclusion: { in: [true, false] }


belongs_to :user
has_many :link_tags
Expand All @@ -19,4 +21,8 @@ def self.create_new(user_id, original)
def self.create_short_link
"tur.link/#{SecureRandom.hex(4)}"
end

def update_privacy(is_private)
update(private: is_private)
end
end
2 changes: 1 addition & 1 deletion app/serializers/link_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class LinkSerializer
include JSONAPI::Serializer
attributes :original, :short, :user_id, :tags, :click_count, :last_click
attributes :original, :short, :user_id, :tags, :click_count, :last_click, :private
end
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
namespace :api do
namespace :v1 do
resources :users, only: %i[create] do
resources :links, only: %i[create index]
resources :links, only: %i[create index] do
member do
patch :update_privacy
end
end
end
resources :sessions, only: %i[create]
resources :links, only: %i[index], action: :show
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20240911225335_add_private_to_links.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddPrivateToLinks < ActiveRecord::Migration[7.1]
def change
add_column :links, :private, :boolean, default: false
end
end
3 changes: 2 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified dump.rdb
Binary file not shown.
34 changes: 30 additions & 4 deletions spec/models/link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,43 @@
it { should have_many(:tags).through(:link_tags) }
end

describe 'class methods' do
describe 'methods' do
describe 'create_new' do
it 'can create a new link' do
user1 = User.create(email: "[email protected]", password: "user123")
link = Link.create_new(user1.id, "long-link-example.com")
user1 = User.create(email: '[email protected]', password: 'user123')
link = Link.create_new(user1.id, 'long-link-example.com')

expect(link).to be_a Link
expect(link.user_id).to eq(user1.id)
expect(link.original).to eq("long-link-example.com")
expect(link.original).to eq('long-link-example.com')
expect(link.short).to be_a String
end
end

describe '.create_short_link' do
it 'creates a unique short link' do
short1 = Link.create_short_link
short2 = Link.create_short_link
expect(short1).not_to eq(short2)
end
end

describe '#update_privacy' do
it 'updates the privacy setting' do
user = User.create!(email: '[email protected]', password: 'password')
link = Link.create!(user:, original: 'https://example.com', short: 'tur.link/abc123', private: false)
link.update_privacy(true)
expect(link.reload.private).to be true
end
end

describe 'default scope' do
it 'excludes disabled links' do
user = User.create!(email: '[email protected]', password: 'password')
Link.create!(user:, original: 'https://example1.com', short: 'tur.link/abc123', disabled: false)
Link.create!(user:, original: 'https://example2.com', short: 'tur.link/def456', disabled: true)
expect(Link.count).to eq(1)
end
end
end
end
48 changes: 48 additions & 0 deletions spec/requests/api/v1/links_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,52 @@
expect(links.last[:attributes][:click_count]).to eq(50)
end
end

describe 'PATCH /users/:user_id/links/:id/update_privacy' do
let(:user) { User.create(email: '[email protected]', password: 'password') }
let(:link) { Link.create(original: 'https://example.com', short: 'tur.link/abc123', user:) }

it 'updates the privacy setting of a link' do
patch "/api/v1/users/#{user.id}/links/#{link.id}/update_privacy", params: { private: 'true' }

expect(response).to be_successful
expect(response.status).to eq(200)

json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:message]).to eq('Privacy setting updated successfully')

link.reload
expect(link.private).to be true
end

it 'returns an error if the user does not own the link' do
other_user = User.create(email: '[email protected]', password: 'password')
patch "/api/v1/users/#{other_user.id}/links/#{link.id}/update_privacy", params: { private: 'true' }

expect(response).to have_http_status(:forbidden)

json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('Unauthorized to update this link')
end

it 'returns an error if the link does not exist' do
patch "/api/v1/users/#{user.id}/links/9999/update_privacy", params: { private: 'true' }

expect(response).to have_http_status(:not_found)

json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('User or Link not found')
end

it 'returns an error if the update fails' do
allow_any_instance_of(Link).to receive(:update).and_return(false)

patch "/api/v1/users/#{user.id}/links/#{link.id}/update_privacy", params: { private: 'true' }

expect(response).to have_http_status(:unprocessable_entity)

json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('Failed to update privacy setting')
end
end
end

0 comments on commit 0ef11cf

Please sign in to comment.