From 326939feee2f608f99e6efa2c6a594e079ca6d5e Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:01:49 -0600 Subject: [PATCH 01/10] feat: add config to switch case sensitivities --- components/dashboard/links/Editor.vue | 4 +++- nuxt.config.ts | 1 + schemas/link.ts | 7 ++++++- server/middleware/1.redirect.ts | 8 ++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/components/dashboard/links/Editor.vue b/components/dashboard/links/Editor.vue index 4f7fb3b..d4eeddd 100644 --- a/components/dashboard/links/Editor.vue +++ b/components/dashboard/links/Editor.vue @@ -102,10 +102,12 @@ onMounted(() => { } }) +const { caseSensitive } = useRuntimeConfig() + async function onSubmit(formData) { const link = { url: formData.url, - slug: formData.slug, + slug: caseSensitive ? formData.slug : formData.slug.toLowerCase(), ...(formData.optional || []), expiration: formData.optional?.expiration ? date2unix(formData.optional?.expiration, 'end') : undefined, } diff --git a/nuxt.config.ts b/nuxt.config.ts index 049414a..30beb15 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -60,6 +60,7 @@ export default defineNuxtConfig({ dataset: 'sink', aiModel: '@cf/meta/llama-3.1-8b-instruct', aiPrompt: `You are a URL shortening assistant, please shorten the URL provided by the user into a SLUG. The SLUG information must come from the URL itself, do not make any assumptions. A SLUG is human-readable and should not exceed three words and can be validated using regular expressions {slugRegex} . Only the best one is returned, the format must be JSON reference {"slug": "example-slug"}`, + caseSensitive: false, public: { previewMode: '', slugDefaultLength: '6', diff --git a/schemas/link.ts b/schemas/link.ts index fe74492..763c650 100644 --- a/schemas/link.ts +++ b/schemas/link.ts @@ -2,10 +2,15 @@ import { z } from 'zod' import { customAlphabet } from 'nanoid' const { slugRegex } = useAppConfig() +const { caseSensitive } = useRuntimeConfig() const slugDefaultLength = +useRuntimeConfig().public.slugDefaultLength -export const nanoid = (length: number = slugDefaultLength) => customAlphabet('23456789abcdefghjkmnpqrstuvwxyz', length) +export function nanoid(length: number = slugDefaultLength) { + return caseSensitive + ? customAlphabet('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ', length) + : customAlphabet('23456789abcdefghjkmnpqrstuvwxyz', length) +} export const LinkSchema = z.object({ id: z.string().trim().max(26).default(nanoid(10)), diff --git a/server/middleware/1.redirect.ts b/server/middleware/1.redirect.ts index 297098d..99ea21f 100644 --- a/server/middleware/1.redirect.ts +++ b/server/middleware/1.redirect.ts @@ -5,7 +5,7 @@ import type { LinkSchema } from '@/schemas/link' export default eventHandler(async (event) => { const { pathname: slug } = parsePath(event.path.replace(/^\/|\/$/g, '')) // remove leading and trailing slashes const { slugRegex, reserveSlug } = useAppConfig(event) - const { homeURL, linkCacheTtl, redirectWithQuery } = useRuntimeConfig(event) + const { homeURL, linkCacheTtl, redirectWithQuery, caseSensitive } = useRuntimeConfig(event) const { cloudflare } = event.context if (event.path === '/' && homeURL) @@ -13,7 +13,11 @@ export default eventHandler(async (event) => { if (slug && !reserveSlug.includes(slug) && slugRegex.test(slug) && cloudflare) { const { KV } = cloudflare.env - const link: z.infer | null = await KV.get(`link:${slug}`, { type: 'json', cacheTtl: linkCacheTtl }) + const link: z.infer | null = await KV.get( + `link:${caseSensitive ? slug : slug.toLowerCase()}`, + { type: 'json', cacheTtl: linkCacheTtl }, + ) + if (link) { event.context.link = link try { From cd7e3022d576eb9834e32e07957d93c780ad7167 Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:02:59 -0600 Subject: [PATCH 02/10] chore: fix wrong key in vscode setting For eslint version > 9 no need to add .experimental --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 898ec90..68d55ed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { // Enable ESlint flat config support - "eslint.experimental.useFlatConfig": true + "eslint.useFlatConfig": true } From e51e912972e5e6fec82c7f68c32cb37a99f346ef Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:22:45 -0600 Subject: [PATCH 03/10] doc: update docs to reflect the new feature. --- README.md | 1 + docs/faqs.md | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 14424b8..82860a3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ - **Customizable Slug:** Support for personalized slugs. - **🪄 AI Slug:** Leverage AI to generate slugs. - **Link Expiration:** Set expiration dates for your links. +- **Switch between case sensitivity:** Toggle between case sensitivity for slugs. ## 🪧 Demo diff --git a/docs/faqs.md b/docs/faqs.md index aa6cde5..159536b 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -32,3 +32,13 @@ Of course. Please set the environment variable `NUXT_HOME_URL` to your blog or o ## 5. Why can't I see statistics after deploying with NuxtHub? NuxtHub's ANALYTICS points to its dataset, you need to set the `NUXT_DATASET` environment variable to point to the same dataset. + +## 6. Why are links always case-insensitive? + +This is a feature of Sink. By default, we automatically convert all links to lowercase to avoid case-sensitive issues and improve usability. This ensures users don’t encounter errors due to accidental capitalization differences. + +However, you can disable this feature by setting the `NUXT_CASE_SENSITIVE` environment variable to `true`. + +### What happens when `NUXT_CASE_SENSITIVE` is `true`? + +Newly generated links will be case-sensitive, treating `MyLink` and `mylink` as distinct. Randomly generated slugs will include both uppercase and lowercase characters, offering a larger pool of unique combinations (but not user-friendly that why we default to non-case-sensitive). From 902fc1bef5a6dee5f78ec045c496b57dbc752157 Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:19:40 -0600 Subject: [PATCH 04/10] fix: forward compability for case sensitive --- server/middleware/1.redirect.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/middleware/1.redirect.ts b/server/middleware/1.redirect.ts index 99ea21f..a8de17a 100644 --- a/server/middleware/1.redirect.ts +++ b/server/middleware/1.redirect.ts @@ -13,10 +13,16 @@ export default eventHandler(async (event) => { if (slug && !reserveSlug.includes(slug) && slugRegex.test(slug) && cloudflare) { const { KV } = cloudflare.env - const link: z.infer | null = await KV.get( - `link:${caseSensitive ? slug : slug.toLowerCase()}`, - { type: 'json', cacheTtl: linkCacheTtl }, - ) + + let link: z.infer | null = null + + const getLink = async (key: string) => + await KV.get(`link:${key}`, { type: 'json', cacheTtl: linkCacheTtl }) + + link = await getLink(slug) + if (!caseSensitive && !link) { + link = await getLink(slug.toLowerCase()) + } if (link) { event.context.link = link From 899323faf6be0696d5dbe03ed9ff65dda14d092d Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:11:07 -0600 Subject: [PATCH 05/10] chore: pre-check if slug is same before and after lowercase. --- server/middleware/1.redirect.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/middleware/1.redirect.ts b/server/middleware/1.redirect.ts index a8de17a..1c3ce80 100644 --- a/server/middleware/1.redirect.ts +++ b/server/middleware/1.redirect.ts @@ -20,8 +20,9 @@ export default eventHandler(async (event) => { await KV.get(`link:${key}`, { type: 'json', cacheTtl: linkCacheTtl }) link = await getLink(slug) - if (!caseSensitive && !link) { - link = await getLink(slug.toLowerCase()) + const lowercaseSlug = slug.toLowerCase() + if (!caseSensitive && !link && lowercaseSlug !== slug) { + link = await getLink(lowercaseSlug) } if (link) { From 172956b532209c27302e1cc71abfee5cff02d7b1 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 8 Dec 2024 11:56:03 +0800 Subject: [PATCH 06/10] feat: add short link to create endpoint response Enhances the link creation API response by including a formatted short link URL Combines protocol, host, and slug to create a complete, ready-to-use short URL --- server/api/link/create.post.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/api/link/create.post.ts b/server/api/link/create.post.ts index 77d7753..8396629 100644 --- a/server/api/link/create.post.ts +++ b/server/api/link/create.post.ts @@ -23,6 +23,7 @@ export default eventHandler(async (event) => { }, }) setResponseStatus(event, 201) - return { link } + const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}` + return { ...link, shortLink } } }) From 337f8f174cbd373b540af33d80b08f3ab835db70 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 8 Dec 2024 12:01:17 +0800 Subject: [PATCH 07/10] fix: refine link creation response structure Returns link object separately from shortLink in response to maintain clearer API structure and prevent unnecessary data duplication in the response payload --- server/api/link/create.post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/link/create.post.ts b/server/api/link/create.post.ts index 8396629..7734e67 100644 --- a/server/api/link/create.post.ts +++ b/server/api/link/create.post.ts @@ -24,6 +24,6 @@ export default eventHandler(async (event) => { }) setResponseStatus(event, 201) const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}` - return { ...link, shortLink } + return { link, shortLink } } }) From e7d71b35d334d8f4c0cbf3ca208616c68f81c838 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 8 Dec 2024 14:44:03 +0800 Subject: [PATCH 08/10] feat: add short link to edit link response Enhances link edit endpoint by including a fully-qualified short link URL in the response, constructed from request protocol and host alongside the slug --- server/api/link/edit.put.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/api/link/edit.put.ts b/server/api/link/edit.put.ts index ca52465..2b05285 100644 --- a/server/api/link/edit.put.ts +++ b/server/api/link/edit.put.ts @@ -30,6 +30,7 @@ export default eventHandler(async (event) => { }, }) setResponseStatus(event, 201) - return { link: newLink } + const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}` + return { link: newLink, shortLink } } }) From 7ab62c6a07547026d82796ebe35b64ba53493610 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 15 Dec 2024 20:38:32 +0800 Subject: [PATCH 09/10] feat: add debug log for case-insensitive slug fallback Adds logging when a case-insensitive URL slug fallback is attempted. This helps track when and how often the fallback mechanism is used. Also improves code readability by renaming variable to match camelCase convention. --- server/middleware/1.redirect.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/middleware/1.redirect.ts b/server/middleware/1.redirect.ts index 1c3ce80..aeebad3 100644 --- a/server/middleware/1.redirect.ts +++ b/server/middleware/1.redirect.ts @@ -20,9 +20,12 @@ export default eventHandler(async (event) => { await KV.get(`link:${key}`, { type: 'json', cacheTtl: linkCacheTtl }) link = await getLink(slug) - const lowercaseSlug = slug.toLowerCase() - if (!caseSensitive && !link && lowercaseSlug !== slug) { - link = await getLink(lowercaseSlug) + + // fallback to lowercase slug if caseSensitive is false and the slug is not found + const lowerCaseSlug = slug.toLowerCase() + if (!caseSensitive && !link && lowerCaseSlug !== slug) { + console.log('lowerCaseSlug fallback:', `slug:${slug} lowerCaseSlug:${lowerCaseSlug}`) + link = await getLink(lowerCaseSlug) } if (link) { From 973d51d4edc3ad11f39b869c9e8c081e368beb28 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 15 Dec 2024 20:39:34 +0800 Subject: [PATCH 10/10] docs: consolidate slug customization features Merges the case sensitivity feature description into the customizable slug feature section for better organization and clarity. This consolidation simplifies the feature list while maintaining all functionality information --- README.md | 3 +-- components/home/Features.vue | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 82860a3..1a635cd 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,9 @@ - **URL Shortening:** Compress your URLs to their minimal length. - **Analytics:** Monitor link analytics and gather insightful statistics. - **Serverless:** Deploy without the need for traditional servers. -- **Customizable Slug:** Support for personalized slugs. +- **Customizable Slug:** Support for personalized slugs and case sensitivity. - **🪄 AI Slug:** Leverage AI to generate slugs. - **Link Expiration:** Set expiration dates for your links. -- **Switch between case sensitivity:** Toggle between case sensitivity for slugs. ## 🪧 Demo diff --git a/components/home/Features.vue b/components/home/Features.vue index d61d183..c983a6d 100644 --- a/components/home/Features.vue +++ b/components/home/Features.vue @@ -23,7 +23,7 @@ const features = ref([ { title: 'Customizable Slug', description: - 'Support for personalized slugs.', + 'Support for personalized slugs and case sensitivity.', icon: Paintbrush, }, {