From 0aed5328135277c1e4741414bafcc20d4b1916ae Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:15:36 -0600 Subject: [PATCH 1/8] test make user links priate --- spec/requests/api/v1/links_request_spec.rb | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/requests/api/v1/links_request_spec.rb b/spec/requests/api/v1/links_request_spec.rb index e1314f3..c5133e8 100644 --- a/spec/requests/api/v1/links_request_spec.rb +++ b/spec/requests/api/v1/links_request_spec.rb @@ -230,4 +230,41 @@ 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: 'user@example.com', 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: 'other@example.com', 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 + end end From 4a38bdd86b89b3017a24031c3428fc180918ed15 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:17:06 -0600 Subject: [PATCH 2/8] feat: migration --- db/migrate/20240911225335_add_private_to_links.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240911225335_add_private_to_links.rb diff --git a/db/migrate/20240911225335_add_private_to_links.rb b/db/migrate/20240911225335_add_private_to_links.rb new file mode 100644 index 0000000..94a694a --- /dev/null +++ b/db/migrate/20240911225335_add_private_to_links.rb @@ -0,0 +1,5 @@ +class AddPrivateToLinks < ActiveRecord::Migration[7.1] + def change + add_column :links, :private, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index e82edf4..9623859 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_08_30_004510) do +ActiveRecord::Schema[7.1].define(version: 2024_09_11_225335) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -32,6 +32,7 @@ t.integer "click_count", default: 0 t.datetime "last_click" t.boolean "disabled", default: false + t.boolean "private", default: false t.index ["user_id"], name: "index_links_on_user_id" end From 8a92c28889224980606034395ce53d290501e725 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:17:52 -0600 Subject: [PATCH 3/8] feat: routes for private link method --- config/routes.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 8cab46d..c16a8fb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 From 775d2ec3519fda278c0803ee51fb0f5c071583e0 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:20:29 -0600 Subject: [PATCH 4/8] feat: method for making a link private and change method for showing a link and top 5 link method --- app/controllers/api/v1/links_controller.rb | 22 +++++++++++++++++++-- app/models/link.rb | 6 ++++++ dump.rdb | Bin 566 -> 566 bytes 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/links_controller.rb b/app/controllers/api/v1/links_controller.rb index e62845a..2092d93 100644 --- a/app/controllers/api/v1/links_controller.rb +++ b/app/controllers/api/v1/links_controller.rb @@ -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) @@ -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? @@ -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 diff --git a/app/models/link.rb b/app/models/link.rb index 8bf2ec2..00af7c5 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -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 @@ -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 diff --git a/dump.rdb b/dump.rdb index 2e6596cc33ef55dde8508a93c4ece41d4df509d2..fae04ea764274e69402048fc2e96ddcc03c342b3 100644 GIT binary patch delta 69 zcmV-L0J{IS1hxc_FeFGi;%52?b#rB8Ep26O!XUE}0Qv}FZ)PoGVRL1`0R8~`0|2q_ bJ^_)nIsy?ck?|#wdpZgKx6~mnoSt2{yha>j delta 72 zcmV-O0Js0P1hxc_FeDaH(q{Syb#rB8Ep26O!Vr%W0Qv}FZ)PoGVRL1`0R8~`0|1lE e0b!Gp0XhN@C6mqpC9!)K0SW&#Fx_DxkxOye5gLO4 From fb6ae8f8efeba1b77f8f206894427f77203cbdd6 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:45:17 -0600 Subject: [PATCH 5/8] feat: updated link serializer --- app/serializers/link_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/link_serializer.rb b/app/serializers/link_serializer.rb index d3c3619..0db10d3 100644 --- a/app/serializers/link_serializer.rb +++ b/app/serializers/link_serializer.rb @@ -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 From 958ca695a5f764d35964420a1641577da861e739 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:48:27 -0600 Subject: [PATCH 6/8] feat: addded model tests --- dump.rdb | Bin 566 -> 566 bytes spec/models/link_spec.rb | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/dump.rdb b/dump.rdb index fae04ea764274e69402048fc2e96ddcc03c342b3..6bdd47bae0b282e4db13f3dcb4f4e8f07e24fa0d 100644 GIT binary patch delta 38 ucmdnSvW;befzUs9>p`1up<9j}Dpu delta 38 ucmdnSvW;befslvQqqJWfrNyZ!y1A*jhZHsoZFG9W$g%y3qTZZ&u{!`L1rO)| diff --git a/spec/models/link_spec.rb b/spec/models/link_spec.rb index db7e8a3..fff5c17 100644 --- a/spec/models/link_spec.rb +++ b/spec/models/link_spec.rb @@ -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: "user@example.com", password: "user123") - link = Link.create_new(user1.id, "long-link-example.com") + user1 = User.create(email: 'user@example.com', 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: 'test@example.com', 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: 'test@example.com', 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 From 08cb456f8681efac534568a4eea226f6e26b152b Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:49:49 -0600 Subject: [PATCH 7/8] feat: added test for making a link private --- spec/requests/api/v1/links_request_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/requests/api/v1/links_request_spec.rb b/spec/requests/api/v1/links_request_spec.rb index c5133e8..32309f3 100644 --- a/spec/requests/api/v1/links_request_spec.rb +++ b/spec/requests/api/v1/links_request_spec.rb @@ -266,5 +266,16 @@ 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 From a3cc07f76abee589384147f40b4bf413c9b673d0 Mon Sep 17 00:00:00 2001 From: Noah Durbin <13364668+noahdurbin@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:57:19 -0600 Subject: [PATCH 8/8] feat: readme update for making a link private --- README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 316518e..4d3897b 100644 --- a/README.md +++ b/README.md @@ -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 } } } @@ -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.