dynamic_links 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7f8c21c9c9432cdd2ed12ddc9e86a79b8f3024a240a406bba89157478520c9d
4
- data.tar.gz: 30e2a4084f34977c4e267aa940454e53978ad193d99214312f90b6ee948fda2d
3
+ metadata.gz: 191decc937ca3ca35b6911558962c6ecea550a22e8032c907f01d1098aced607
4
+ data.tar.gz: 6f983f6713bc399e51fb5990fa888e7a2165b21ed09ec00097d99ee92173ba7e
5
5
  SHA512:
6
- metadata.gz: 1deecaad47165bae46df566e68661bc77444aa36d143084d7e1a6aa76d05958e9183b1f88a9a083813404778e9f305d8588887a1558b8d8f98c1ec66c1c07e10
7
- data.tar.gz: bc0572b2ce3274ada41824867284e47d82263db2b2e2c323c8306f1c5487cd30040fe3b677b950880d17b026a997daa3907bbd060d3fdda3db83076f1566fbe3
6
+ metadata.gz: 55a751b1dc66a5687d91e430888efd4f96f83ad6e227dca63a1e2c571958131640eae71b5524ebf50e4330c2f9bb6f803dfa28cf413f607aee23f682bd18ff6c
7
+ data.tar.gz: 261cc6051c78d7a736c560fbc5ffa14eac49c3f0b031a8f1f516b5bbd1fa181f5d26b391fe819c23ef05b6d1c77fe2ae49f7867bef5f7aaf86a2cf387135fa51
data/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.0] - 2025-07-25
8
+
9
+ - [#101](https://github.com/saiqulhaq/dynamic_links/pull/101)
10
+
11
+ - New Feature: Added `find_or_create` REST API endpoint that finds existing short links or creates new ones
12
+ - New Feature: Added `DynamicLinks.find_short_link` method to search for existing short links by URL and client
13
+ - Enhancement: Improved URL validation in REST API controllers with dedicated `valid_url?` method
14
+ - Enhancement: Code cleanup - removed unnecessary hash brackets in `find_by` calls
15
+
7
16
  ## [0.2.0] - 2025-06-17
8
17
 
9
18
  - [#88](https://github.com/saiqulhaq/dynamic_links/pull/88)
data/README.md CHANGED
@@ -88,57 +88,150 @@ To shorten a URL, simply call:
88
88
  shortened_url = DynamicLinks.shorten_url("https://example.com")
89
89
  ```
90
90
 
91
- ### Expanding a Short URL
91
+ ### Finding an Existing Short Link
92
92
 
93
- To expand (resolve) a short URL back to its original URL, use the `resolve_short_url` method:
93
+ To find an existing short link for a URL:
94
94
 
95
95
  ```ruby
96
- original_url = DynamicLinks.resolve_short_url("abc123")
97
- # Returns the original URL or nil if not found
96
+ short_link_data = DynamicLinks.find_short_link("https://example.com", client)
97
+ if short_link_data
98
+ puts short_link_data[:short_url] # e.g., "https://client.com/abc123"
99
+ puts short_link_data[:full_url] # e.g., "https://example.com"
100
+ else
101
+ puts "No existing short link found"
102
+ end
98
103
  ```
99
104
 
100
- #### Using the REST API to Expand URLs
105
+ ## REST API
106
+
107
+ DynamicLinks provides a REST API for URL shortening operations when `enable_rest_api` is set to `true` in the configuration.
108
+
109
+ ### Authentication
110
+
111
+ All API endpoints require an `api_key` parameter that corresponds to a registered client.
112
+
113
+ ### Endpoints
114
+
115
+ #### Create Short Link
116
+
117
+ Creates a new short link for a URL.
118
+
119
+ **Endpoint:** `POST /v1/shortLinks`
120
+
121
+ **Parameters:**
122
+
123
+ - `url` (required): The URL to shorten
124
+ - `api_key` (required): Client API key
125
+
126
+ **Example Request:**
127
+
128
+ ```bash
129
+ curl -X POST "http://localhost:3000/v1/shortLinks" \
130
+ -H "Content-Type: application/json" \
131
+ -d '{
132
+ "url": "https://example.com/long-url",
133
+ "api_key": "your-api-key"
134
+ }'
135
+ ```
101
136
 
102
- The gem also provides a REST API endpoint to expand short URLs:
137
+ **Example Response:**
103
138
 
139
+ ```json
140
+ {
141
+ "shortLink": "https://your-domain.com/abc123",
142
+ "previewLink": "https://your-domain.com/abc123?preview=true",
143
+ "warning": []
144
+ }
104
145
  ```
105
- GET /v1/shortLinks/:short_url?api_key=YOUR_API_KEY
146
+
147
+ #### Find or Create Short Link
148
+
149
+ Finds an existing short link for a URL, or creates a new one if none exists. This prevents duplicate short links for the same URL and client.
150
+
151
+ **Endpoint:** `POST /v1/shortLinks/findOrCreate`
152
+
153
+ **Parameters:**
154
+
155
+ - `url` (required): The URL to find or shorten
156
+ - `api_key` (required): Client API key
157
+
158
+ **Example Request:**
159
+
160
+ ```bash
161
+ curl -X POST "http://localhost:3000/v1/shortLinks/findOrCreate" \
162
+ -H "Content-Type: application/json" \
163
+ -d '{
164
+ "url": "https://example.com/long-url",
165
+ "api_key": "your-api-key"
166
+ }'
106
167
  ```
107
168
 
108
- Example response:
169
+ **Example Response (existing link found):**
109
170
 
110
171
  ```json
111
172
  {
112
- "full_url": "https://example.com/original-path"
173
+ "shortLink": "https://your-domain.com/abc123",
174
+ "previewLink": "https://your-domain.com/abc123?preview=true",
175
+ "warning": []
113
176
  }
114
177
  ```
115
178
 
116
- If the URL is not found, you'll receive a 404 status code with an error message:
179
+ **Example Response (new link created):**
117
180
 
118
181
  ```json
119
182
  {
120
- "error": "Short link not found"
183
+ "shortLink": "https://your-domain.com/def456",
184
+ "previewLink": "https://your-domain.com/def456?preview=true",
185
+ "warning": []
121
186
  }
122
187
  ```
123
188
 
124
- ## Fallback Mode
189
+ #### Expand Short Link
125
190
 
126
- The fallback mode feature (added in PR #88) allows your application to redirect users to a Firebase Dynamic Links URL when a short URL is not found in your system. This can be useful in migration scenarios or when running both systems in parallel.
191
+ Retrieves the original URL from a short link.
127
192
 
128
- To enable this feature:
193
+ **Endpoint:** `GET /v1/shortLinks/{short_url}`
129
194
 
130
- ```ruby
131
- DynamicLinks.configure do |config|
132
- config.enable_fallback_mode = true
133
- config.firebase_host = "https://your-app.page.link" # Your Firebase Dynamic Links URL
134
- end
195
+ **Parameters:**
196
+
197
+ - `short_url` (in URL): The short URL code to expand
198
+ - `api_key` (required): Client API key
199
+
200
+ **Example Request:**
201
+
202
+ ```bash
203
+ curl "http://localhost:3000/v1/shortLinks/abc123?api_key=your-api-key"
204
+ ```
205
+
206
+ **Example Response:**
207
+
208
+ ```json
209
+ {
210
+ "url": "https://example.com/long-url"
211
+ }
135
212
  ```
136
213
 
137
- When a user visits a short link that doesn't exist in your database, they will be redirected to the equivalent Firebase URL instead of receiving a 404 error.
214
+ ### Error Responses
215
+
216
+ The API returns appropriate HTTP status codes and error messages:
217
+
218
+ - `400 Bad Request`: Invalid URL format
219
+ - `401 Unauthorized`: Invalid or missing API key
220
+ - `403 Forbidden`: REST API feature is disabled
221
+ - `404 Not Found`: Short link not found (expand endpoint)
222
+ - `500 Internal Server Error`: Server error
223
+
224
+ **Example Error Response:**
225
+
226
+ ```json
227
+ {
228
+ "error": "Invalid URL"
229
+ }
230
+ ```
138
231
 
139
232
  ## Available Shortening Strategies
140
233
 
141
- DynamicLinks supports various shortening strategies. The default strategy is now `NanoIdStrategy` (changed from `MD5` in PR #88), but you can choose among several others, including `MD5Strategy`, `RedisCounterStrategy`, `Sha256Strategy`, and more.
234
+ DynamicLinks supports various shortening strategies. The default strategy is `MD5`, but you can choose among several others, including `NanoIdStrategy`, `RedisCounterStrategy`, `Sha256Strategy`, and more.
142
235
 
143
236
  Depending on the strategy you choose, you may need to install additional dependencies.
144
237
 
@@ -171,8 +264,13 @@ $ gem install dynamic_links
171
264
 
172
265
  ## Performance
173
266
 
174
- Shorten an URL using Ruby:
175
- Shorten an URL using API:
267
+ Benchmarking scripts are available in the `benchmarks/` directory to measure performance:
268
+
269
+ - `ruby_api.rb`: Benchmarks Ruby API URL shortening performance
270
+ - `rest_api.py`: Benchmarks REST API URL shortening performance
271
+ - `create_or_find.rb`: Compares performance of different `create_or_find` methods
272
+
273
+ You can run these benchmarks to measure performance in your specific environment.
176
274
 
177
275
  ## How to run the unit test
178
276
 
@@ -7,7 +7,9 @@ module DynamicLinks
7
7
  yield
8
8
  end
9
9
  else
10
- Rails.logger.warn 'MultiTenant gem is not installed. Please install it to use sharding strategy'
10
+ # Rails.logger.warn 'MultiTenant gem is not installed. Please install it to use sharding strategy'
11
+ DynamicLinks::Logger.log_warn('MultiTenant gem is not installed. Please install it to use sharding strategy')
12
+
11
13
  yield
12
14
  end
13
15
  else
@@ -4,7 +4,7 @@ module DynamicLinks
4
4
 
5
5
  def create
6
6
  url = params.require(:url)
7
- client = DynamicLinks::Client.find_by({ api_key: params.require(:api_key) })
7
+ client = DynamicLinks::Client.find_by(api_key: params.require(:api_key))
8
8
 
9
9
  unless client
10
10
  render json: { error: 'Invalid API key' }, status: :unauthorized
@@ -23,7 +23,7 @@ module DynamicLinks
23
23
 
24
24
  def expand
25
25
  api_key = params.require(:api_key)
26
- client = DynamicLinks::Client.find_by({ api_key: api_key })
26
+ client = DynamicLinks::Client.find_by(api_key: api_key)
27
27
 
28
28
  unless client
29
29
  render json: { error: 'Invalid API key' }, status: :unauthorized
@@ -45,6 +45,38 @@ module DynamicLinks
45
45
  render json: { error: 'An error occurred while processing your request' }, status: :internal_server_error
46
46
  end
47
47
 
48
+ def find_or_create
49
+ url = params.require(:url)
50
+ client = DynamicLinks::Client.find_by(api_key: params.require(:api_key))
51
+
52
+ unless client
53
+ render json: { error: 'Invalid API key' }, status: :unauthorized
54
+ return
55
+ end
56
+
57
+ unless valid_url?(url)
58
+ render json: { error: 'Invalid URL' }, status: :bad_request
59
+ return
60
+ end
61
+
62
+ multi_tenant(client) do
63
+ short_link = DynamicLinks.find_short_link(url, client)
64
+
65
+ if short_link
66
+ render json: {
67
+ shortLink: short_link[:short_url],
68
+ previewLink: "#{short_link[:short_url]}?preview=true",
69
+ warning: []
70
+ }, status: :ok
71
+ else
72
+ render json: DynamicLinks.generate_short_url(url, client), status: :created
73
+ end
74
+ end
75
+ rescue => e
76
+ DynamicLinks::Logger.log_error(e)
77
+ render json: { error: 'An error occurred while processing your request' }, status: :internal_server_error
78
+ end
79
+
48
80
  private
49
81
 
50
82
  def check_rest_api_enabled
@@ -52,5 +84,12 @@ module DynamicLinks
52
84
  render json: { error: 'REST API feature is disabled' }, status: :forbidden
53
85
  end
54
86
  end
87
+
88
+ def valid_url?(url)
89
+ uri = URI.parse(url)
90
+ uri.is_a?(URI::HTTP) && uri.host.present? && uri.scheme.present?
91
+ rescue URI::InvalidURIError
92
+ false
93
+ end
55
94
  end
56
95
  end
data/config/routes.rb CHANGED
@@ -5,6 +5,7 @@ DynamicLinks::Engine.routes.draw do
5
5
  get '/:short_url', to: 'redirects#show', as: :shortened
6
6
  namespace :v1 do
7
7
  post "/shortLinks", to: "short_links#create", as: :short_links
8
+ post "/shortLinks/findOrCreate", to: "short_links#find_or_create", as: :find_or_create_short_link
8
9
  get "/shortLinks/:short_url", to: "short_links#expand", as: :expand_short_link
9
10
  end
10
11
  end
@@ -25,7 +25,7 @@ module DynamicLinks
25
25
  # config.async_processing = false # or true. if true, the shortening process will be done asynchronously using ActiveJob
26
26
  # config.redis_counter_config = RedisConfig.new # see RedisConfig documentation for more details
27
27
  # # if you use Redis
28
- # config.cache_store = ActiveSupport::Cache::RedisStore.new('redis://localhost:6379/0/cache')
28
+ # config.cache_store = ActiveSupport::Cache::RedisCacheStore.new(url: 'redis://localhost:6379/0/cache')
29
29
  # # if you use Memcached
30
30
  # config.cache_store = ActiveSupport::Cache::MemCacheStore.new('localhost:11211')
31
31
  # end
@@ -44,7 +44,10 @@ module DynamicLinks
44
44
  end
45
45
 
46
46
  def shortening_strategy=(strategy)
47
- raise ArgumentError, "Invalid shortening strategy" unless StrategyFactory::VALID_SHORTENING_STRATEGIES.include?(strategy)
47
+ unless StrategyFactory::VALID_SHORTENING_STRATEGIES.include?(strategy)
48
+ raise ArgumentError, "Invalid shortening strategy, provided strategy: #{strategy}. Valid strategies are: #{StrategyFactory::VALID_SHORTENING_STRATEGIES.join(', ')}"
49
+ end
50
+
48
51
  @shortening_strategy = strategy
49
52
  end
50
53
 
@@ -1,3 +1,3 @@
1
1
  module DynamicLinks
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/dynamic_links.rb CHANGED
@@ -75,4 +75,16 @@ module DynamicLinks
75
75
  def self.resolve_short_url(short_link)
76
76
  DynamicLinks::ShortenedUrl.find_by(short_url: short_link)&.url
77
77
  end
78
+
79
+ def self.find_short_link(long_url, client)
80
+ short_link = DynamicLinks::ShortenedUrl.find_by(url: long_url, client_id: client.id)
81
+ if short_link
82
+ {
83
+ short_url: "#{client.scheme}://#{client.hostname}/#{short_link.short_url}",
84
+ full_url: long_url
85
+ }
86
+ else
87
+ nil
88
+ end
89
+ end
78
90
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_links
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saiqul Haq
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-17 00:00:00.000000000 Z
11
+ date: 2025-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails