@linkforty/core 1.0.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.
- package/LICENSE +21 -0
- package/README.md +459 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/database.d.ts +11 -0
- package/dist/lib/database.d.ts.map +1 -0
- package/dist/lib/database.js +118 -0
- package/dist/lib/database.js.map +1 -0
- package/dist/lib/utils.d.ts +26 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +119 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/routes/analytics.d.ts +3 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +171 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +10 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/links.d.ts +3 -0
- package/dist/routes/links.d.ts.map +1 -0
- package/dist/routes/links.js +179 -0
- package/dist/routes/links.js.map +1 -0
- package/dist/routes/redirect.d.ts +3 -0
- package/dist/routes/redirect.d.ts.map +1 -0
- package/dist/routes/redirect.js +138 -0
- package/dist/routes/redirect.js.map +1 -0
- package/dist/scripts/migrate.d.ts +2 -0
- package/dist/scripts/migrate.d.ts.map +1 -0
- package/dist/scripts/migrate.js +17 -0
- package/dist/scripts/migrate.js.map +1 -0
- package/dist/types/index.d.ts +139 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 LinkForty
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# @linkforty/core
|
|
2
|
+
|
|
3
|
+
**Open-source deeplink management engine with device detection and analytics**
|
|
4
|
+
|
|
5
|
+
LinkForty Core is a powerful, self-hosted deeplink management system that enables you to create, manage, and track smart links with device-specific routing, analytics, and UTM parameter support. It's the open-source foundation of the LinkForty platform.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@linkforty/core)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
✅ **Smart Link Routing** - Create short links with device-specific URLs for iOS, Android, and web \
|
|
13
|
+
✅ **Device Detection** - Automatic detection and routing based on user device \
|
|
14
|
+
✅ **Click Analytics** - Track clicks with geolocation, device type, platform, and more \
|
|
15
|
+
✅ **UTM Parameters** - Built-in support for UTM campaign tracking \
|
|
16
|
+
✅ **Link Expiration** - Set expiration dates for time-sensitive links \
|
|
17
|
+
✅ **Redis Caching** - Optional Redis support for high-performance link lookups \
|
|
18
|
+
✅ **PostgreSQL Storage** - Reliable data persistence with full SQL capabilities \
|
|
19
|
+
✅ **TypeScript** - Fully typed API for better developer experience
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @linkforty/core
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Basic Server
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createServer } from '@linkforty/core';
|
|
33
|
+
|
|
34
|
+
async function start() {
|
|
35
|
+
const server = await createServer({
|
|
36
|
+
database: {
|
|
37
|
+
url: 'postgresql://localhost/linkforty',
|
|
38
|
+
},
|
|
39
|
+
redis: {
|
|
40
|
+
url: 'redis://localhost:6379',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await server.listen({ port: 3000, host: '0.0.0.0' });
|
|
45
|
+
console.log('Server running on http://localhost:3000');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
start();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Self-Hosting with Docker
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Clone the examples
|
|
55
|
+
git clone https://github.com/linkforty/core.git
|
|
56
|
+
cd core/examples
|
|
57
|
+
|
|
58
|
+
# Start services
|
|
59
|
+
docker-compose up -d
|
|
60
|
+
|
|
61
|
+
# Server will be available at http://localhost:3000
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API Reference
|
|
65
|
+
|
|
66
|
+
### Create a Link
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
POST /api/links
|
|
70
|
+
Content-Type: application/json
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
"userId": "user-uuid",
|
|
74
|
+
"originalUrl": "https://example.com",
|
|
75
|
+
"title": "My Link",
|
|
76
|
+
"iosUrl": "myapp://product/123",
|
|
77
|
+
"androidUrl": "myapp://product/123",
|
|
78
|
+
"webFallbackUrl": "https://example.com/product/123",
|
|
79
|
+
"utmParameters": {
|
|
80
|
+
"source": "twitter",
|
|
81
|
+
"medium": "social",
|
|
82
|
+
"campaign": "summer-sale"
|
|
83
|
+
},
|
|
84
|
+
"customCode": "summer-sale",
|
|
85
|
+
"expiresAt": "2024-12-31T23:59:59Z"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Response:**
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"id": "link-uuid",
|
|
94
|
+
"userId": "user-uuid",
|
|
95
|
+
"short_code": "summer-sale",
|
|
96
|
+
"original_url": "https://example.com",
|
|
97
|
+
"title": "My Link",
|
|
98
|
+
"ios_url": "myapp://product/123",
|
|
99
|
+
"android_url": "myapp://product/123",
|
|
100
|
+
"web_fallback_url": "https://example.com/product/123",
|
|
101
|
+
"utmParameters": {
|
|
102
|
+
"source": "twitter",
|
|
103
|
+
"medium": "social",
|
|
104
|
+
"campaign": "summer-sale"
|
|
105
|
+
},
|
|
106
|
+
"is_active": true,
|
|
107
|
+
"expires_at": "2024-12-31T23:59:59Z",
|
|
108
|
+
"created_at": "2024-01-01T00:00:00Z",
|
|
109
|
+
"updated_at": "2024-01-01T00:00:00Z",
|
|
110
|
+
"clickCount": 0
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Get All Links
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
GET /api/links?userId=user-uuid
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Get a Specific Link
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
GET /api/links/:id?userId=user-uuid
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Update a Link
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
PUT /api/links/:id?userId=user-uuid
|
|
130
|
+
Content-Type: application/json
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
"title": "Updated Title",
|
|
134
|
+
"isActive": false
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Delete a Link
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
DELETE /api/links/:id?userId=user-uuid
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Get Analytics Overview
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
GET /api/analytics/overview?userId=user-uuid&days=30
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Response:**
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"totalClicks": 1234,
|
|
155
|
+
"uniqueClicks": 567,
|
|
156
|
+
"clicksByDate": [
|
|
157
|
+
{ "date": "2024-01-01", "clicks": 45 }
|
|
158
|
+
],
|
|
159
|
+
"clicksByCountry": [
|
|
160
|
+
{ "countryCode": "US", "country": "United States", "clicks": 234 }
|
|
161
|
+
],
|
|
162
|
+
"clicksByDevice": [
|
|
163
|
+
{ "device": "mobile", "clicks": 789 }
|
|
164
|
+
],
|
|
165
|
+
"clicksByPlatform": [
|
|
166
|
+
{ "platform": "iOS", "clicks": 456 }
|
|
167
|
+
],
|
|
168
|
+
"topLinks": [
|
|
169
|
+
{
|
|
170
|
+
"id": "link-uuid",
|
|
171
|
+
"shortCode": "summer-sale",
|
|
172
|
+
"title": "My Link",
|
|
173
|
+
"originalUrl": "https://example.com",
|
|
174
|
+
"totalClicks": 123,
|
|
175
|
+
"uniqueClicks": 67
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Get Link-Specific Analytics
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
GET /api/analytics/links/:linkId?userId=user-uuid&days=30
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Redirect Short Link
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
GET /:shortCode
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This endpoint automatically redirects users to the appropriate URL based on their device type.
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
### Server Options
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface ServerOptions {
|
|
201
|
+
database?: {
|
|
202
|
+
url?: string; // PostgreSQL connection string
|
|
203
|
+
pool?: {
|
|
204
|
+
min?: number; // Minimum pool connections (default: 2)
|
|
205
|
+
max?: number; // Maximum pool connections (default: 10)
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
redis?: {
|
|
209
|
+
url: string; // Redis connection string (optional)
|
|
210
|
+
};
|
|
211
|
+
cors?: {
|
|
212
|
+
origin: string | string[]; // CORS allowed origins (default: '*')
|
|
213
|
+
};
|
|
214
|
+
logger?: boolean; // Enable Fastify logger (default: true)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Environment Variables
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
DATABASE_URL=postgresql://localhost/linkforty
|
|
222
|
+
REDIS_URL=redis://localhost:6379
|
|
223
|
+
PORT=3000
|
|
224
|
+
NODE_ENV=production
|
|
225
|
+
CORS_ORIGIN=*
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Database Schema
|
|
229
|
+
|
|
230
|
+
### Users Table
|
|
231
|
+
|
|
232
|
+
| Column | Type | Description |
|
|
233
|
+
|---------------|--------------|-----------------------|
|
|
234
|
+
| id | UUID | Primary key |
|
|
235
|
+
| email | VARCHAR(255) | Unique email |
|
|
236
|
+
| name | VARCHAR(255) | User name |
|
|
237
|
+
| password_hash | VARCHAR(255) | Hashed password |
|
|
238
|
+
| created_at | TIMESTAMP | Creation timestamp |
|
|
239
|
+
| updated_at | TIMESTAMP | Last update timestamp |
|
|
240
|
+
|
|
241
|
+
### Links Table
|
|
242
|
+
|
|
243
|
+
| Column | Type | Description |
|
|
244
|
+
|------------------|--------------|-----------------------|
|
|
245
|
+
| id | UUID | Primary key |
|
|
246
|
+
| user_id | UUID | Foreign key to users |
|
|
247
|
+
| short_code | VARCHAR(20) | Unique short code |
|
|
248
|
+
| original_url | TEXT | Original URL |
|
|
249
|
+
| title | VARCHAR(255) | Link title |
|
|
250
|
+
| ios_url | TEXT | iOS-specific URL |
|
|
251
|
+
| android_url | TEXT | Android-specific URL |
|
|
252
|
+
| web_fallback_url | TEXT | Web fallback URL |
|
|
253
|
+
| utm_parameters | JSONB | UTM parameters |
|
|
254
|
+
| targeting_rules | JSONB | Targeting rules |
|
|
255
|
+
| is_active | BOOLEAN | Active status |
|
|
256
|
+
| expires_at | TIMESTAMP | Expiration date |
|
|
257
|
+
| created_at | TIMESTAMP | Creation timestamp |
|
|
258
|
+
| updated_at | TIMESTAMP | Last update timestamp |
|
|
259
|
+
|
|
260
|
+
### Click Events Table
|
|
261
|
+
|
|
262
|
+
| Column | Type | Description |
|
|
263
|
+
|--------------|--------------|------------------------------|
|
|
264
|
+
| id | UUID | Primary key |
|
|
265
|
+
| link_id | UUID | Foreign key to links |
|
|
266
|
+
| clicked_at | TIMESTAMP | Click timestamp |
|
|
267
|
+
| ip_address | INET | User IP address |
|
|
268
|
+
| user_agent | TEXT | User agent string |
|
|
269
|
+
| device_type | VARCHAR(20) | Device type (mobile/desktop) |
|
|
270
|
+
| platform | VARCHAR(20) | Platform (iOS/Android/Web) |
|
|
271
|
+
| country_code | CHAR(2) | Country code |
|
|
272
|
+
| country_name | VARCHAR(100) | Country name |
|
|
273
|
+
| region | VARCHAR(100) | Region/state |
|
|
274
|
+
| city | VARCHAR(100) | City |
|
|
275
|
+
| latitude | DECIMAL | Latitude |
|
|
276
|
+
| longitude | DECIMAL | Longitude |
|
|
277
|
+
| timezone | VARCHAR(100) | Timezone |
|
|
278
|
+
| utm_source | VARCHAR(255) | UTM source |
|
|
279
|
+
| utm_medium | VARCHAR(255) | UTM medium |
|
|
280
|
+
| utm_campaign | VARCHAR(255) | UTM campaign |
|
|
281
|
+
| referrer | TEXT | Referrer URL |
|
|
282
|
+
|
|
283
|
+
## Utilities
|
|
284
|
+
|
|
285
|
+
### Generate Short Code
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { generateShortCode } from '@linkforty/core';
|
|
289
|
+
|
|
290
|
+
const code = generateShortCode(8); // Returns 8-character nanoid
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Detect Device
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { detectDevice } from '@linkforty/core';
|
|
297
|
+
|
|
298
|
+
const device = detectDevice(userAgent); // Returns 'ios' | 'android' | 'web'
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Get Location from IP
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { getLocationFromIP } from '@linkforty/core';
|
|
305
|
+
|
|
306
|
+
const location = getLocationFromIP('8.8.8.8');
|
|
307
|
+
// Returns: { countryCode, countryName, region, city, latitude, longitude, timezone }
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Build Redirect URL with UTM Parameters
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { buildRedirectUrl } from '@linkforty/core';
|
|
314
|
+
|
|
315
|
+
const url = buildRedirectUrl('https://example.com', {
|
|
316
|
+
source: 'twitter',
|
|
317
|
+
medium: 'social',
|
|
318
|
+
campaign: 'summer-sale'
|
|
319
|
+
});
|
|
320
|
+
// Returns: https://example.com?utm_source=twitter&utm_medium=social&utm_campaign=summer-sale
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Advanced Usage
|
|
324
|
+
|
|
325
|
+
### Custom Route Registration
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { createServer } from '@linkforty/core';
|
|
329
|
+
|
|
330
|
+
const server = await createServer({
|
|
331
|
+
database: { url: 'postgresql://localhost/linkforty' },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Add custom routes
|
|
335
|
+
server.get('/custom', async (request, reply) => {
|
|
336
|
+
return { message: 'Hello World' };
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await server.listen({ port: 3000 });
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Using Individual Route Handlers
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import Fastify from 'fastify';
|
|
346
|
+
import { initializeDatabase, redirectRoutes, linkRoutes } from '@linkforty/core';
|
|
347
|
+
|
|
348
|
+
const fastify = Fastify();
|
|
349
|
+
|
|
350
|
+
// Initialize database separately
|
|
351
|
+
await initializeDatabase({ url: 'postgresql://localhost/linkforty' });
|
|
352
|
+
|
|
353
|
+
// Register only specific routes
|
|
354
|
+
await fastify.register(redirectRoutes);
|
|
355
|
+
await fastify.register(linkRoutes);
|
|
356
|
+
|
|
357
|
+
await fastify.listen({ port: 3000 });
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Deployment
|
|
361
|
+
|
|
362
|
+
LinkForty can be deployed in multiple ways depending on your needs:
|
|
363
|
+
|
|
364
|
+
### 🚀 Production Deployment (Recommended)
|
|
365
|
+
|
|
366
|
+
Deploy to managed platforms with minimal DevOps overhead:
|
|
367
|
+
|
|
368
|
+
**Fly.io (Recommended)**
|
|
369
|
+
- Global edge deployment
|
|
370
|
+
- Managed PostgreSQL and Redis
|
|
371
|
+
- Auto-scaling and SSL included
|
|
372
|
+
- Starting at ~$10-15/month
|
|
373
|
+
|
|
374
|
+
[View Fly.io deployment guide →](infra/fly.io/DEPLOYMENT.md)
|
|
375
|
+
|
|
376
|
+
See [`infra/`](infra/) directory for all deployment options and platform-specific guides.
|
|
377
|
+
|
|
378
|
+
### Docker Deployment (Self-Hosted)
|
|
379
|
+
|
|
380
|
+
For local development or self-managed infrastructure:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
git clone https://github.com/linkforty/core.git
|
|
384
|
+
cd core/examples
|
|
385
|
+
docker-compose up -d
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
See [`examples/docker-compose.yml`](examples/docker-compose.yml) for complete Docker setup.
|
|
389
|
+
|
|
390
|
+
### Manual Deployment
|
|
391
|
+
|
|
392
|
+
For custom infrastructure needs:
|
|
393
|
+
|
|
394
|
+
1. Install dependencies: `npm install @linkforty/core`
|
|
395
|
+
2. Set up PostgreSQL database (13+)
|
|
396
|
+
3. Set up Redis (optional but recommended)
|
|
397
|
+
4. Run migrations: `npm run migrate`
|
|
398
|
+
5. Start server: `node server.js`
|
|
399
|
+
|
|
400
|
+
### Other Platforms
|
|
401
|
+
|
|
402
|
+
Community-maintained templates available for:
|
|
403
|
+
- AWS (ECS/Fargate)
|
|
404
|
+
- Google Cloud Run
|
|
405
|
+
- Railway, Render, and more
|
|
406
|
+
|
|
407
|
+
See [`infra/CONTRIBUTING.md`](infra/CONTRIBUTING.md) to add support for additional platforms.
|
|
408
|
+
|
|
409
|
+
## Performance
|
|
410
|
+
|
|
411
|
+
- **Redis caching**: 5-minute TTL on link lookups reduces database queries by 90%
|
|
412
|
+
- **Database indexes**: Optimized queries for fast link lookups and analytics
|
|
413
|
+
- **Async click tracking**: Non-blocking click event logging
|
|
414
|
+
- **Connection pooling**: Efficient database connection management
|
|
415
|
+
|
|
416
|
+
## Security
|
|
417
|
+
|
|
418
|
+
- **SQL injection protection**: Parameterized queries throughout
|
|
419
|
+
- **Input validation**: Zod schema validation on all inputs
|
|
420
|
+
- **CORS configuration**: Configurable CORS for API access control
|
|
421
|
+
- **Link expiration**: Automatic handling of expired links
|
|
422
|
+
|
|
423
|
+
## Roadmap
|
|
424
|
+
|
|
425
|
+
- [ ] Webhook support for click events
|
|
426
|
+
- [ ] Bulk link operations via API
|
|
427
|
+
- [ ] Link grouping and tags
|
|
428
|
+
- [ ] A/B testing support
|
|
429
|
+
- [ ] QR code generation
|
|
430
|
+
- [ ] Custom domain support (in SaaS version)
|
|
431
|
+
|
|
432
|
+
## Contributing
|
|
433
|
+
|
|
434
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
439
|
+
|
|
440
|
+
## Related Projects
|
|
441
|
+
|
|
442
|
+
- **@linkforty/ui** - React UI components for link management
|
|
443
|
+
- **[LinkForty Cloud](https://linkforty.com)** - Hosted SaaS version with additional features
|
|
444
|
+
|
|
445
|
+
## Support
|
|
446
|
+
|
|
447
|
+
- **Documentation**: [https://docs.linkforty.com](https://docs.linkforty.com)
|
|
448
|
+
- **Issues**: [GitHub Issues](https://github.com/linkforty/core/issues)
|
|
449
|
+
- **Discussions**: [GitHub Discussions](https://github.com/linkforty/core/discussions)
|
|
450
|
+
|
|
451
|
+
## Built with:
|
|
452
|
+
- [Fastify](https://www.fastify.io/) - Fast web framework
|
|
453
|
+
- [PostgreSQL](https://www.postgresql.org/) - Powerful database
|
|
454
|
+
- [Redis](https://redis.io/) - In-memory cache
|
|
455
|
+
- [nanoid](https://github.com/ai/nanoid) - Unique ID generation
|
|
456
|
+
- [geoip-lite](https://github.com/geoip-lite/node-geoip) - IP geolocation
|
|
457
|
+
- [ua-parser-js](https://github.com/faisalman/ua-parser-js) - User agent parsing
|
|
458
|
+
|
|
459
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
import { DatabaseOptions } from './lib/database.js';
|
|
3
|
+
export interface ServerOptions {
|
|
4
|
+
database?: DatabaseOptions;
|
|
5
|
+
redis?: {
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
cors?: {
|
|
9
|
+
origin: string | string[];
|
|
10
|
+
};
|
|
11
|
+
logger?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function createServer(options?: ServerOptions): Promise<FastifyInstance<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
|
|
14
|
+
export * from './lib/utils.js';
|
|
15
|
+
export * from './lib/database.js';
|
|
16
|
+
export * from './types/index.js';
|
|
17
|
+
export { redirectRoutes, linkRoutes, analyticsRoutes } from './routes/index.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAsB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKxE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,aAAkB,kTA0B7D;AAGD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.analyticsRoutes = exports.linkRoutes = exports.redirectRoutes = void 0;
|
|
21
|
+
exports.createServer = createServer;
|
|
22
|
+
const fastify_1 = __importDefault(require("fastify"));
|
|
23
|
+
const cors_1 = __importDefault(require("@fastify/cors"));
|
|
24
|
+
const redis_1 = __importDefault(require("@fastify/redis"));
|
|
25
|
+
const database_js_1 = require("./lib/database.js");
|
|
26
|
+
const redirect_js_1 = require("./routes/redirect.js");
|
|
27
|
+
const links_js_1 = require("./routes/links.js");
|
|
28
|
+
const analytics_js_1 = require("./routes/analytics.js");
|
|
29
|
+
async function createServer(options = {}) {
|
|
30
|
+
const fastify = (0, fastify_1.default)({
|
|
31
|
+
logger: options.logger !== undefined ? options.logger : true,
|
|
32
|
+
});
|
|
33
|
+
// CORS
|
|
34
|
+
await fastify.register(cors_1.default, {
|
|
35
|
+
origin: options.cors?.origin || '*',
|
|
36
|
+
});
|
|
37
|
+
// Redis (optional)
|
|
38
|
+
if (options.redis?.url) {
|
|
39
|
+
await fastify.register(redis_1.default, {
|
|
40
|
+
url: options.redis.url,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Database
|
|
44
|
+
await (0, database_js_1.initializeDatabase)(options.database);
|
|
45
|
+
// Routes
|
|
46
|
+
await fastify.register(redirect_js_1.redirectRoutes);
|
|
47
|
+
await fastify.register(links_js_1.linkRoutes);
|
|
48
|
+
await fastify.register(analytics_js_1.analyticsRoutes);
|
|
49
|
+
return fastify;
|
|
50
|
+
}
|
|
51
|
+
// Re-export utilities and types
|
|
52
|
+
__exportStar(require("./lib/utils.js"), exports);
|
|
53
|
+
__exportStar(require("./lib/database.js"), exports);
|
|
54
|
+
__exportStar(require("./types/index.js"), exports);
|
|
55
|
+
var index_js_1 = require("./routes/index.js");
|
|
56
|
+
Object.defineProperty(exports, "redirectRoutes", { enumerable: true, get: function () { return index_js_1.redirectRoutes; } });
|
|
57
|
+
Object.defineProperty(exports, "linkRoutes", { enumerable: true, get: function () { return index_js_1.linkRoutes; } });
|
|
58
|
+
Object.defineProperty(exports, "analyticsRoutes", { enumerable: true, get: function () { return index_js_1.analyticsRoutes; } });
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,oCA0BC;AA7CD,sDAAmD;AACnD,yDAAiC;AACjC,2DAAmC;AACnC,mDAAwE;AACxE,sDAAsD;AACtD,gDAA+C;AAC/C,wDAAwD;AAajD,KAAK,UAAU,YAAY,CAAC,UAAyB,EAAE;IAC5D,MAAM,OAAO,GAAG,IAAA,iBAAO,EAAC;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;KAC7D,CAAC,CAAC;IAEH,OAAO;IACP,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAI,EAAE;QAC3B,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,IAAI,GAAG;KACpC,CAAC,CAAC;IAEH,mBAAmB;IACnB,IAAI,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,CAAC,QAAQ,CAAC,eAAK,EAAE;YAC5B,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG;SACvB,CAAC,CAAC;IACL,CAAC;IAED,WAAW;IACX,MAAM,IAAA,gCAAkB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE3C,SAAS;IACT,MAAM,OAAO,CAAC,QAAQ,CAAC,4BAAc,CAAC,CAAC;IACvC,MAAM,OAAO,CAAC,QAAQ,CAAC,qBAAU,CAAC,CAAC;IACnC,MAAM,OAAO,CAAC,QAAQ,CAAC,8BAAe,CAAC,CAAC;IAExC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,gCAAgC;AAChC,iDAA+B;AAC/B,oDAAkC;AAClC,mDAAiC;AACjC,8CAAgF;AAAvE,0GAAA,cAAc,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,2GAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
export interface DatabaseOptions {
|
|
3
|
+
url?: string;
|
|
4
|
+
pool?: {
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare let db: pg.Pool;
|
|
10
|
+
export declare function initializeDatabase(options?: DatabaseOptions): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/lib/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,eAAO,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;AA6BvB,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,eAAoB,iBAoFrE"}
|