Skip to content

Commit cc1718c

Browse files
committed
v0.0.1
1 parent 26d6215 commit cc1718c

File tree

11 files changed

+165
-44
lines changed

11 files changed

+165
-44
lines changed

netlify.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[build]
22
publish = "public"
3-
functions = "netlify/functions"
43

54
[build.environment]
65
NODE_VERSION = "20"
@@ -17,6 +16,12 @@
1716
path = "/latest"
1817
function = "latest"
1918

19+
[[edge_functions]]
20+
path = "/hello"
21+
function = "hello"
22+
23+
2024
[[edge_functions]]
2125
path = "/:shortUrl"
22-
function = "redirect"
26+
function = "redirect"
27+

netlify/edge-functions/count.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { headers } from "./headers.ts";
22
import handler from "./utils.ts";
3-
43
const { fetchFromSupabase } = handler();
54

65
export default async (request: Request): Promise<Response> => {
76
try {
8-
const data = await fetchFromSupabase("urls?select=id", { method: "GET" });
9-
const count = data.length;
7+
const response = await fetchFromSupabase("urls?select=id", {
8+
method: "GET",
9+
});
10+
if (!response || !Array.isArray(response)) {
11+
throw new Error("Invalid response from Supabase");
12+
}
13+
const count = response.length;
1014
return new Response(JSON.stringify({ count }), { status: 200, headers });
1115
} catch (err) {
16+
console.error("Error fetching count:", err);
1217
return new Response(
1318
JSON.stringify({ error: "Internal server error", details: err.message }),
1419
{ status: 500, headers }
1520
);
1621
}
1722
};
23+
export const config = { path: "/count" };

netlify/edge-functions/headers.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
export const headers = {
2-
"Access-Control-Allow-Origin": "*",
3-
"Access-Control-Allow-Methods": "*",
4-
"Access-Control-Allow-Headers": "*",
5-
"Access-Control-Max-Age": "86400",
6-
"Content-Type": "application/json",
2+
'Access-Control-Allow-Origin': '*',
3+
'Access-Control-Allow-Methods': '*',
4+
'Access-Control-Allow-Headers': '*',
5+
'Access-Control-Max-Age': '86400',
6+
'Content-Type': 'application/json',
77
};
8-
98
export default () => headers;

netlify/edge-functions/hello.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default async (request: Request): Promise<Response> => {
2+
console.log("Hello function triggered");
3+
return new Response("Hello, World!", {
4+
headers: {
5+
"Content-Type": "text/plain",
6+
},
7+
});
8+
};
9+
10+
export const config = {
11+
path: "/hello",
12+
};

netlify/edge-functions/latest.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
import { headers } from "./headers.ts";
22
import handler from "./utils.ts";
3-
43
const urlBase = Deno.env.get("URL_BASE") ? Deno.env.get("URL_BASE") : "";
54
const { fetchFromSupabase } = handler();
65

76
export default async (request: Request): Promise<Response> => {
7+
console.log("here");
88
try {
99
const url = new URL(request.url);
1010
const count = url.searchParams.get("count") || "10";
11-
const data = await fetchFromSupabase(
11+
12+
const response = await fetchFromSupabase(
1213
`urls?order=created_at.desc&limit=${count}`,
1314
{ method: "GET" }
1415
);
15-
const modifiedData = data.map((item: any) => ({
16+
17+
if (!response || !Array.isArray(response)) {
18+
throw new Error("Invalid response from Supabase");
19+
}
20+
21+
const modifiedResponse = response.map((item: any) => ({
1622
...item,
1723
short_url: `${urlBase}${item.short_url}`,
1824
}));
1925

20-
return new Response(JSON.stringify(modifiedData), { status: 200, headers });
26+
return new Response(JSON.stringify(modifiedResponse), {
27+
status: 200,
28+
headers,
29+
});
2130
} catch (err) {
2231
return new Response(
2332
JSON.stringify({ error: "Internal server error", details: err.message }),
2433
{ status: 500, headers }
2534
);
2635
}
2736
};
37+
export const config = { path: "/latest" };

netlify/edge-functions/redirect.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import handler from "./utils.ts";
2-
32
const { fetchFromSupabase } = handler();
4-
53
export default async (request: Request): Promise<Response> => {
64
try {
75
const shortUrl = new URL(request.url).pathname.replace("/", "");
@@ -18,10 +16,14 @@ export default async (request: Request): Promise<Response> => {
1816
status: 301,
1917
headers: { Location: data[0].long_url },
2018
});
21-
} catch (err) {
19+
} catch (error) {
2220
return new Response(
23-
JSON.stringify({ error: "Internal server error", details: err.message }),
21+
JSON.stringify({
22+
error: "Internal server error",
23+
details: error.message,
24+
}),
2425
{ status: 500 }
2526
);
2627
}
2728
};
29+
export const config = { path: "/" };

netlify/edge-functions/shorten.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { multiParser } from "https://deno.land/x/multiparser@0.114.0/mod.ts";
22
import { headers } from "./headers.ts";
33
import handler from "./utils.ts";
4-
5-
const urlBase = Deno.env.get("URL_BASE") ? Deno.env.get("URL_BASE") : "";
6-
const { fetchFromSupabase, generateShortUrl } = handler();
4+
const urlBase = Deno.env.get("URL_BASE") || "";
5+
const { generateShortUrl } = handler();
76

87
export default async (request: Request): Promise<Response> => {
98
try {
@@ -17,20 +16,20 @@ export default async (request: Request): Promise<Response> => {
1716
});
1817
}
1918

20-
let shortUrl = generateShortUrl();
21-
shortUrl = urlBase + shortUrl;
22-
23-
await fetchFromSupabase("urls", {
24-
method: "POST",
25-
headers: { "Content-Type": "application/json" },
26-
body: JSON.stringify({ short_url: shortUrl, long_url: url }),
27-
});
19+
let shortUrl = urlBase + (await generateShortUrl(url));
2820

2921
return new Response(JSON.stringify({ shortUrl }), { status: 200, headers });
30-
} catch (err) {
22+
} catch (error) {
23+
console.error("Error in function execution:", error);
24+
3125
return new Response(
32-
JSON.stringify({ error: "Internal server error", details: err.message }),
26+
JSON.stringify({
27+
error: "Internal server error",
28+
details: error.message,
29+
}),
3330
{ status: 500, headers }
3431
);
3532
}
3633
};
34+
35+
export const config = { path: "/shorten" };

netlify/edge-functions/utils.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export async function fetchFromSupabase(
55
endpoint: string,
66
options: RequestInit
77
) {
8+
console.log("options", options);
89
const response = await fetch(`${SUPABASE_URL}/rest/v1/${endpoint}`, {
910
...options,
1011
headers: {
@@ -13,16 +14,67 @@ export async function fetchFromSupabase(
1314
Authorization: `Bearer ${SUPABASE_KEY}`,
1415
},
1516
});
16-
return await response.json();
17-
}
1817

19-
export function generateShortUrl(): string {
18+
if (!response.ok) {
19+
throw new Error(`Supabase request failed: ${response.statusText}`);
20+
}
21+
let jsonResponse = await response.json();
22+
console.log("jsonResponse", jsonResponse);
23+
return jsonResponse;
24+
}
25+
import {
26+
fetchFromSupabase,
27+
generateShortUrl as generateUuidShort,
28+
} from "./utils.ts";
29+
export async function generateShortUrl(longUrl: string): Promise<string> {
30+
try {
31+
let { data, error } = await fetchFromSupabase(
32+
"urls?select=short_url&long_url=eq." + longUrl,
33+
{ method: "GET" }
34+
);
35+
if (error) {
36+
console.error("Error checking for existing long URL:", error);
37+
throw error;
38+
}
39+
if (data && data[0]?.short_url) {
40+
return data[0].short_url;
41+
}
42+
let shortUrl: string;
43+
let isCollision = true;
44+
while (isCollision) {
45+
shortUrl = generateUuidShort();
46+
({ data, error } = await fetchFromSupabase(
47+
"urls?select=short_url&short_url=eq." + shortUrl,
48+
{ method: "GET" }
49+
));
50+
if (error) {
51+
console.error("Error checking for collision:", error);
52+
throw error;
53+
}
54+
isCollision = data.length > 0;
55+
}
56+
({ data, error } = await fetchFromSupabase("urls", {
57+
method: "POST",
58+
headers: { "Content-Type": "application/json" },
59+
body: JSON.stringify({ short_url: shortUrl, long_url: longUrl }),
60+
}));
61+
if (error) {
62+
console.error("Error inserting new URL:", error);
63+
throw error;
64+
}
65+
return shortUrl;
66+
} catch (err) {
67+
console.error("Error in generateShortUrl function:", err);
68+
throw err;
69+
}
70+
}
71+
export function generateUuidShort(): string {
2072
return crypto.randomUUID().substring(0, 7);
2173
}
22-
2374
export default function handler() {
2475
return {
2576
fetchFromSupabase,
2677
generateShortUrl,
78+
generateShortUrl,
2779
};
2880
}

package-lock.json

Lines changed: 19 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
11
{
22
"name": "url-shortening-api-netlify-edge-supabase",
3-
"version": "1.0.0",
3+
"version": "0.0.1",
44
"description": "",
5-
"main": "index.js",
5+
"main": "public/index.html",
66
"scripts": {
77
"test": "echo \"Error: no test specified\" && exit 1"
88
},
9-
"keywords": [],
10-
"author": "",
11-
"license": "ISC",
9+
"keywords": [
10+
"netlify",
11+
"lambda",
12+
"netlify edge function",
13+
"netlify edge",
14+
"supabase",
15+
"url shortener",
16+
"url",
17+
"rest",
18+
"api"
19+
],
20+
"author": "Sam Estrin",
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/samestrin/url-shortening-api-netlify-edge-supabase/issues"
24+
},
25+
"homepage": "https://github.com/samestrin/url-shortening-api-netlify-edge-supabase#readme",
26+
"repository": {
27+
"type": "git",
28+
"url": "git+https://github.com/samestrin/url-shortening-api-netlify-edge-supabase.git"
29+
},
1230
"devDependencies": {
31+
"prettier": "^3.2.5",
1332
"typescript": "^5.4.5"
1433
}
1534
}

0 commit comments

Comments
 (0)