@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.
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.linkRoutes = linkRoutes;
4
+ const zod_1 = require("zod");
5
+ const database_js_1 = require("../lib/database.js");
6
+ const utils_js_1 = require("../lib/utils.js");
7
+ const createLinkSchema = zod_1.z.object({
8
+ userId: zod_1.z.string().uuid(),
9
+ originalUrl: zod_1.z.string().url(),
10
+ title: zod_1.z.string().optional(),
11
+ iosUrl: zod_1.z.string().url().optional(),
12
+ androidUrl: zod_1.z.string().url().optional(),
13
+ webFallbackUrl: zod_1.z.string().url().optional(),
14
+ customCode: zod_1.z.string().optional(),
15
+ utmParameters: zod_1.z.object({
16
+ source: zod_1.z.string().optional(),
17
+ medium: zod_1.z.string().optional(),
18
+ campaign: zod_1.z.string().optional(),
19
+ term: zod_1.z.string().optional(),
20
+ content: zod_1.z.string().optional(),
21
+ }).optional(),
22
+ targetingRules: zod_1.z.object({
23
+ countries: zod_1.z.array(zod_1.z.string()).optional(),
24
+ devices: zod_1.z.array(zod_1.z.enum(['ios', 'android', 'web'])).optional(),
25
+ languages: zod_1.z.array(zod_1.z.string()).optional(),
26
+ }).optional(),
27
+ expiresAt: zod_1.z.string().datetime().optional(),
28
+ });
29
+ const updateLinkSchema = createLinkSchema.partial().extend({
30
+ isActive: zod_1.z.boolean().optional(),
31
+ }).omit({ userId: true });
32
+ async function linkRoutes(fastify) {
33
+ // Get all links for a user
34
+ fastify.get('/api/links', async (request) => {
35
+ const { userId } = request.query;
36
+ if (!userId) {
37
+ throw new Error('userId query parameter is required');
38
+ }
39
+ const query = `
40
+ SELECT l.*,
41
+ COUNT(ce.id) as click_count
42
+ FROM links l
43
+ LEFT JOIN click_events ce ON l.id = ce.link_id
44
+ WHERE l.user_id = $1
45
+ GROUP BY l.id
46
+ ORDER BY l.created_at DESC
47
+ `;
48
+ const result = await database_js_1.db.query(query, [userId]);
49
+ return result.rows.map(row => ({
50
+ ...row,
51
+ clickCount: parseInt(row.click_count),
52
+ utmParameters: row.utm_parameters,
53
+ targetingRules: row.targeting_rules,
54
+ }));
55
+ });
56
+ // Get single link
57
+ fastify.get('/api/links/:id', async (request) => {
58
+ const { id } = request.params;
59
+ const { userId } = request.query;
60
+ if (!userId) {
61
+ throw new Error('userId query parameter is required');
62
+ }
63
+ const result = await database_js_1.db.query(`SELECT l.*, COUNT(ce.id) as click_count
64
+ FROM links l
65
+ LEFT JOIN click_events ce ON l.id = ce.link_id
66
+ WHERE l.id = $1 AND l.user_id = $2
67
+ GROUP BY l.id`, [id, userId]);
68
+ if (result.rows.length === 0) {
69
+ throw new Error('Link not found');
70
+ }
71
+ const link = result.rows[0];
72
+ return {
73
+ ...link,
74
+ clickCount: parseInt(link.click_count),
75
+ utmParameters: link.utm_parameters,
76
+ targetingRules: link.targeting_rules,
77
+ };
78
+ });
79
+ // Create link
80
+ fastify.post('/api/links', async (request) => {
81
+ const data = createLinkSchema.parse(request.body);
82
+ // Generate short code
83
+ let shortCode = data.customCode || (0, utils_js_1.generateShortCode)();
84
+ // Ensure short code is unique
85
+ let attempts = 0;
86
+ while (attempts < 10) {
87
+ const existing = await database_js_1.db.query('SELECT id FROM links WHERE short_code = $1', [shortCode]);
88
+ if (existing.rows.length === 0) {
89
+ break;
90
+ }
91
+ shortCode = (0, utils_js_1.generateShortCode)();
92
+ attempts++;
93
+ }
94
+ if (attempts >= 10) {
95
+ throw new Error('Unable to generate unique short code');
96
+ }
97
+ const result = await database_js_1.db.query(`INSERT INTO links (
98
+ user_id, short_code, original_url, title,
99
+ ios_url, android_url, web_fallback_url, utm_parameters, targeting_rules, expires_at
100
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
101
+ RETURNING *`, [
102
+ data.userId,
103
+ shortCode,
104
+ data.originalUrl,
105
+ data.title || null,
106
+ data.iosUrl || null,
107
+ data.androidUrl || null,
108
+ data.webFallbackUrl || null,
109
+ JSON.stringify(data.utmParameters || {}),
110
+ JSON.stringify(data.targetingRules || {}),
111
+ data.expiresAt || null,
112
+ ]);
113
+ const link = result.rows[0];
114
+ return {
115
+ ...link,
116
+ clickCount: 0,
117
+ utmParameters: link.utm_parameters,
118
+ targetingRules: link.targeting_rules,
119
+ };
120
+ });
121
+ // Update link
122
+ fastify.put('/api/links/:id', async (request) => {
123
+ const { id } = request.params;
124
+ const { userId } = request.query;
125
+ if (!userId) {
126
+ throw new Error('userId query parameter is required');
127
+ }
128
+ const data = updateLinkSchema.parse(request.body);
129
+ // Build update query dynamically
130
+ const updates = [];
131
+ const values = [];
132
+ let paramIndex = 1;
133
+ Object.entries(data).forEach(([key, value]) => {
134
+ if (value !== undefined) {
135
+ if (key === 'utmParameters' || key === 'targetingRules') {
136
+ updates.push(`${key.replace(/([A-Z])/g, '_$1').toLowerCase()} = $${paramIndex}`);
137
+ values.push(JSON.stringify(value));
138
+ }
139
+ else {
140
+ const dbKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
141
+ updates.push(`${dbKey} = $${paramIndex}`);
142
+ values.push(value);
143
+ }
144
+ paramIndex++;
145
+ }
146
+ });
147
+ if (updates.length === 0) {
148
+ throw new Error('No updates provided');
149
+ }
150
+ updates.push('updated_at = NOW()');
151
+ values.push(id, userId);
152
+ const result = await database_js_1.db.query(`UPDATE links SET ${updates.join(', ')}
153
+ WHERE id = $${paramIndex} AND user_id = $${paramIndex + 1}
154
+ RETURNING *`, values);
155
+ if (result.rows.length === 0) {
156
+ throw new Error('Link not found');
157
+ }
158
+ const link = result.rows[0];
159
+ return {
160
+ ...link,
161
+ utmParameters: link.utm_parameters,
162
+ targetingRules: link.targeting_rules,
163
+ };
164
+ });
165
+ // Delete link
166
+ fastify.delete('/api/links/:id', async (request) => {
167
+ const { id } = request.params;
168
+ const { userId } = request.query;
169
+ if (!userId) {
170
+ throw new Error('userId query parameter is required');
171
+ }
172
+ const result = await database_js_1.db.query('DELETE FROM links WHERE id = $1 AND user_id = $2 RETURNING id', [id, userId]);
173
+ if (result.rows.length === 0) {
174
+ throw new Error('Link not found');
175
+ }
176
+ return { success: true };
177
+ });
178
+ }
179
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/routes/links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export declare function redirectRoutes(fastify: FastifyInstance): Promise<void>;
3
+ //# sourceMappingURL=redirect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,iBA4J5D"}
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redirectRoutes = redirectRoutes;
4
+ const database_js_1 = require("../lib/database.js");
5
+ const utils_js_1 = require("../lib/utils.js");
6
+ async function redirectRoutes(fastify) {
7
+ // Handle short link redirects
8
+ fastify.get('/:shortCode', async (request, reply) => {
9
+ const { shortCode } = request.params;
10
+ let linkData = null;
11
+ // Try to get link from cache if Redis is available
12
+ const cacheKey = `link:${shortCode}`;
13
+ if (fastify.redis) {
14
+ try {
15
+ linkData = await fastify.redis.get(cacheKey);
16
+ }
17
+ catch (error) {
18
+ fastify.log.warn('Redis cache lookup failed, falling back to database');
19
+ }
20
+ }
21
+ if (!linkData) {
22
+ // Get from database
23
+ const result = await database_js_1.db.query(`SELECT * FROM links
24
+ WHERE short_code = $1 AND is_active = true
25
+ AND (expires_at IS NULL OR expires_at > NOW())`, [shortCode]);
26
+ if (result.rows.length === 0) {
27
+ return reply.status(404).send({ error: 'Link not found' });
28
+ }
29
+ linkData = JSON.stringify(result.rows[0]);
30
+ // Cache for 5 minutes if Redis is available
31
+ if (fastify.redis) {
32
+ try {
33
+ await fastify.redis.setex(cacheKey, 300, linkData);
34
+ }
35
+ catch (error) {
36
+ fastify.log.warn('Redis cache set failed');
37
+ }
38
+ }
39
+ }
40
+ const link = JSON.parse(linkData);
41
+ // Check targeting rules BEFORE redirecting
42
+ if (link.targeting_rules) {
43
+ const userAgent = request.headers['user-agent'] || '';
44
+ const ip = request.ip;
45
+ const acceptLanguage = request.headers['accept-language'] || '';
46
+ // Get user's actual data for targeting checks
47
+ const device = (0, utils_js_1.detectDevice)(userAgent);
48
+ const { countryCode } = (0, utils_js_1.getLocationFromIP)(ip);
49
+ // Extract primary language from accept-language header (e.g., "en-US,en;q=0.9" -> "en")
50
+ const primaryLanguage = acceptLanguage.split(',')[0]?.split('-')[0]?.toLowerCase();
51
+ const rules = link.targeting_rules;
52
+ let isTargeted = true;
53
+ // Check country targeting
54
+ if (rules.countries && rules.countries.length > 0) {
55
+ const targetCountries = rules.countries.map((c) => c.toUpperCase());
56
+ if (!countryCode || !targetCountries.includes(countryCode.toUpperCase())) {
57
+ isTargeted = false;
58
+ }
59
+ }
60
+ // Check device targeting
61
+ if (rules.devices && rules.devices.length > 0) {
62
+ if (!rules.devices.includes(device)) {
63
+ isTargeted = false;
64
+ }
65
+ }
66
+ // Check language targeting
67
+ if (rules.languages && rules.languages.length > 0) {
68
+ const targetLanguages = rules.languages.map((l) => l.toLowerCase());
69
+ if (!primaryLanguage || !targetLanguages.includes(primaryLanguage)) {
70
+ isTargeted = false;
71
+ }
72
+ }
73
+ // If targeting rules exist but user doesn't match, return 404
74
+ if (!isTargeted) {
75
+ return reply.status(404).send({ error: 'Link not found' });
76
+ }
77
+ }
78
+ // Track click asynchronously
79
+ setImmediate(async () => {
80
+ try {
81
+ const userAgent = request.headers['user-agent'] || '';
82
+ const ip = request.ip;
83
+ const referrer = request.headers.referer || null;
84
+ const { deviceType, platform } = (0, utils_js_1.parseUserAgent)(userAgent);
85
+ const { countryCode, countryName, region, city, latitude, longitude, timezone } = (0, utils_js_1.getLocationFromIP)(ip);
86
+ // Extract UTM parameters from query string
87
+ const query = request.query;
88
+ const utmSource = query?.utm_source;
89
+ const utmMedium = query?.utm_medium;
90
+ const utmCampaign = query?.utm_campaign;
91
+ await database_js_1.db.query(`INSERT INTO click_events (
92
+ link_id, ip_address, user_agent, device_type, platform,
93
+ country_code, country_name, region, city, latitude, longitude, timezone,
94
+ utm_source, utm_medium, utm_campaign, referrer
95
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`, [
96
+ link.id,
97
+ ip,
98
+ userAgent,
99
+ deviceType,
100
+ platform,
101
+ countryCode,
102
+ countryName,
103
+ region,
104
+ city,
105
+ latitude,
106
+ longitude,
107
+ timezone,
108
+ utmSource,
109
+ utmMedium,
110
+ utmCampaign,
111
+ referrer,
112
+ ]);
113
+ }
114
+ catch (error) {
115
+ fastify.log.error(`Error tracking click: ${error}`);
116
+ }
117
+ });
118
+ // Determine redirect URL based on device
119
+ const userAgent = request.headers['user-agent'] || '';
120
+ const device = (0, utils_js_1.detectDevice)(userAgent);
121
+ let redirectUrl = link.original_url;
122
+ // Check for device-specific URLs
123
+ if (device === 'ios' && link.ios_url) {
124
+ redirectUrl = link.ios_url;
125
+ }
126
+ else if (device === 'android' && link.android_url) {
127
+ redirectUrl = link.android_url;
128
+ }
129
+ else if (link.web_fallback_url && device === 'web') {
130
+ redirectUrl = link.web_fallback_url;
131
+ }
132
+ // Add UTM parameters
133
+ const finalUrl = (0, utils_js_1.buildRedirectUrl)(redirectUrl, link.utm_parameters);
134
+ // Redirect
135
+ return reply.redirect(302, finalUrl);
136
+ });
137
+ }
138
+ //# sourceMappingURL=redirect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/scripts/migrate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const database_js_1 = require("../lib/database.js");
4
+ async function runMigrations() {
5
+ try {
6
+ console.log('Starting database migration...');
7
+ await (0, database_js_1.initializeDatabase)();
8
+ console.log('Database migration completed successfully!');
9
+ process.exit(0);
10
+ }
11
+ catch (error) {
12
+ console.error('Migration failed:', error);
13
+ process.exit(1);
14
+ }
15
+ }
16
+ runMigrations();
17
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/scripts/migrate.ts"],"names":[],"mappings":";;AAAA,oDAAwD;AAExD,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,MAAM,IAAA,gCAAkB,GAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,aAAa,EAAE,CAAC"}
@@ -0,0 +1,139 @@
1
+ export interface User {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ createdAt: string;
6
+ updatedAt: string;
7
+ }
8
+ export interface Link {
9
+ id: string;
10
+ userId: string;
11
+ short_code: string;
12
+ original_url: string;
13
+ title?: string;
14
+ ios_url?: string;
15
+ android_url?: string;
16
+ web_fallback_url?: string;
17
+ utmParameters?: UTMParameters;
18
+ targeting_rules?: TargetingRules;
19
+ is_active: boolean;
20
+ expires_at?: string;
21
+ created_at: string;
22
+ updated_at: string;
23
+ click_count?: number;
24
+ }
25
+ export interface UTMParameters {
26
+ source?: string;
27
+ medium?: string;
28
+ campaign?: string;
29
+ term?: string;
30
+ content?: string;
31
+ }
32
+ export interface TargetingRules {
33
+ countries?: string[];
34
+ devices?: ('ios' | 'android' | 'web')[];
35
+ languages?: string[];
36
+ }
37
+ export interface ClickEvent {
38
+ id: string;
39
+ linkId: string;
40
+ clickedAt: string;
41
+ ipAddress?: string;
42
+ userAgent?: string;
43
+ deviceType?: string;
44
+ platform?: string;
45
+ countryCode?: string;
46
+ countryName?: string;
47
+ region?: string;
48
+ city?: string;
49
+ latitude?: number;
50
+ longitude?: number;
51
+ timezone?: string;
52
+ utmSource?: string;
53
+ utmMedium?: string;
54
+ utmCampaign?: string;
55
+ referrer?: string;
56
+ }
57
+ export interface CreateLinkRequest {
58
+ originalUrl: string;
59
+ title?: string;
60
+ iosUrl?: string;
61
+ androidUrl?: string;
62
+ webFallbackUrl?: string;
63
+ utmParameters?: UTMParameters;
64
+ targetingRules?: TargetingRules;
65
+ customCode?: string;
66
+ expiresAt?: string;
67
+ }
68
+ export interface UpdateLinkRequest extends Partial<CreateLinkRequest> {
69
+ isActive?: boolean;
70
+ }
71
+ export interface AnalyticsData {
72
+ totalClicks: number;
73
+ uniqueClicks: number;
74
+ clicksByDate: Array<{
75
+ date: string;
76
+ clicks: number;
77
+ }>;
78
+ clicksByCountry: Array<{
79
+ country: string;
80
+ countryCode: string;
81
+ clicks: number;
82
+ }>;
83
+ clicksByCity: Array<{
84
+ city: string;
85
+ countryCode: string;
86
+ region: string;
87
+ clicks: number;
88
+ }>;
89
+ clicksByRegion: Array<{
90
+ region: string;
91
+ countryCode: string;
92
+ clicks: number;
93
+ }>;
94
+ clicksByTimezone: Array<{
95
+ timezone: string;
96
+ clicks: number;
97
+ }>;
98
+ clicksByDevice: Array<{
99
+ device: string;
100
+ clicks: number;
101
+ }>;
102
+ clicksByPlatform: Array<{
103
+ platform: string;
104
+ clicks: number;
105
+ }>;
106
+ clicksByBrowser: Array<{
107
+ browser: string;
108
+ clicks: number;
109
+ }>;
110
+ clicksByHour: Array<{
111
+ hour: number;
112
+ clicks: number;
113
+ }>;
114
+ clicksByUtmSource: Array<{
115
+ source: string;
116
+ clicks: number;
117
+ }>;
118
+ clicksByUtmMedium: Array<{
119
+ medium: string;
120
+ clicks: number;
121
+ }>;
122
+ clicksByUtmCampaign: Array<{
123
+ campaign: string;
124
+ clicks: number;
125
+ }>;
126
+ clicksByReferrer: Array<{
127
+ source: string;
128
+ clicks: number;
129
+ }>;
130
+ topLinks: Array<{
131
+ id: string;
132
+ shortCode: string;
133
+ title: string | null;
134
+ originalUrl: string;
135
+ totalClicks: number;
136
+ uniqueClicks: number;
137
+ }>;
138
+ }
139
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@linkforty/core",
3
+ "version": "1.0.0",
4
+ "description": "Open-source deeplink management engine with device detection and analytics",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/linkforty/core"
11
+ },
12
+ "keywords": [
13
+ "deeplink",
14
+ "onelink",
15
+ "mobile-links",
16
+ "device-detection",
17
+ "url-shortener",
18
+ "analytics"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsx watch src/index.ts",
23
+ "migrate": "tsx src/scripts/migrate.ts",
24
+ "test": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "fastify": "^4.24.0",
28
+ "@fastify/cors": "^8.4.0",
29
+ "@fastify/redis": "^6.1.0",
30
+ "pg": "^8.11.0",
31
+ "nanoid": "^5.0.0",
32
+ "geoip-lite": "^1.4.7",
33
+ "ua-parser-js": "^1.0.37",
34
+ "zod": "^3.22.0",
35
+ "dotenv": "^16.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.8.0",
39
+ "@types/pg": "^8.10.0",
40
+ "@types/geoip-lite": "^1.4.4",
41
+ "@types/ua-parser-js": "^0.7.39",
42
+ "tsx": "^4.0.0",
43
+ "typescript": "^5.2.0",
44
+ "vitest": "^1.0.0"
45
+ },
46
+ "exports": {
47
+ ".": "./dist/index.js",
48
+ "./utils": "./dist/lib/utils.js",
49
+ "./database": "./dist/lib/database.js",
50
+ "./routes": "./dist/routes/index.js",
51
+ "./types": "./dist/types/index.js"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "migrations",
56
+ "README.md",
57
+ "LICENSE"
58
+ ]
59
+ }