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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +121 -23
- data/app/controllers/dynamic_links/application_controller.rb +3 -1
- data/app/controllers/dynamic_links/v1/short_links_controller.rb +41 -2
- data/config/routes.rb +1 -0
- data/lib/dynamic_links/configuration.rb +5 -2
- data/lib/dynamic_links/version.rb +1 -1
- data/lib/dynamic_links.rb +12 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 191decc937ca3ca35b6911558962c6ecea550a22e8032c907f01d1098aced607
|
4
|
+
data.tar.gz: 6f983f6713bc399e51fb5990fa888e7a2165b21ed09ec00097d99ee92173ba7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
###
|
91
|
+
### Finding an Existing Short Link
|
92
92
|
|
93
|
-
To
|
93
|
+
To find an existing short link for a URL:
|
94
94
|
|
95
95
|
```ruby
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
169
|
+
**Example Response (existing link found):**
|
109
170
|
|
110
171
|
```json
|
111
172
|
{
|
112
|
-
"
|
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
|
-
|
179
|
+
**Example Response (new link created):**
|
117
180
|
|
118
181
|
```json
|
119
182
|
{
|
120
|
-
"
|
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
|
-
|
189
|
+
#### Expand Short Link
|
125
190
|
|
126
|
-
|
191
|
+
Retrieves the original URL from a short link.
|
127
192
|
|
128
|
-
|
193
|
+
**Endpoint:** `GET /v1/shortLinks/{short_url}`
|
129
194
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
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
|
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
|
-
|
175
|
-
|
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(
|
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(
|
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::
|
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
|
-
|
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
|
|
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.
|
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-
|
11
|
+
date: 2025-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|