Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pull #98

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open

pull #98

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
// Enable ESlint flat config support
"eslint.experimental.useFlatConfig": true
"eslint.useFlatConfig": true
}
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
- **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.

Expand Down Expand Up @@ -71,7 +71,8 @@ We welcome your contributions and PRs.
- [Sink Tool](https://github.com/zhuzhuyule/sink-extension)
- [x] Raycast Extension
- [Raycast-Sink](https://github.com/foru17/raycast-sink)
- [ ] Apple Shortcuts
- [x] Apple Shortcuts
- [Sink Shortcuts](https://s.search1api.com/sink001)
- [ ] Enhanced Link Management (with Cloudflare D1)
- [ ] Analytics Enhancements (Support for merging filter conditions)
- [ ] Dashboard Performance Optimization (Infinite loading)
Expand Down Expand Up @@ -100,6 +101,7 @@ We welcome your contributions and PRs.
- Bind the variable name `ANALYTICS` to the `sink` dataset.

7. Redeploy the project.
8. Update code, refer to the official GitHub documentation [Syncing a fork branch from the web UI](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui).

## ⚒️ Configuration

Expand Down
2 changes: 1 addition & 1 deletion components/home/Features.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const features = ref([
{
title: 'Customizable Slug',
description:
'Support for personalized slugs.',
'Support for personalized slugs and case sensitivity.',
icon: Paintbrush,
},
{
Expand Down
10 changes: 10 additions & 0 deletions docs/faqs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
1 change: 1 addition & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sink",
"type": "module",
"version": "0.1.4",
"version": "0.1.6",
"private": true,
"packageManager": "[email protected]",
"engines": {
Expand Down
7 changes: 6 additions & 1 deletion schemas/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
3 changes: 2 additions & 1 deletion server/api/link/create.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
})
3 changes: 2 additions & 1 deletion server/api/link/edit.put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
})
2 changes: 1 addition & 1 deletion server/api/link/list.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default eventHandler(async (event) => {
const list = await KV.list({
prefix: `link:`,
limit,
cursor,
cursor: cursor || undefined,
})
if (Array.isArray(list.keys)) {
list.links = await Promise.all(list.keys.map(async (key: { name: string }) => {
Expand Down
18 changes: 16 additions & 2 deletions server/middleware/1.redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,29 @@ 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)
return sendRedirect(event, homeURL)

if (slug && !reserveSlug.includes(slug) && slugRegex.test(slug) && cloudflare) {
const { KV } = cloudflare.env
const link: z.infer<typeof LinkSchema> | null = await KV.get(`link:${slug}`, { type: 'json', cacheTtl: linkCacheTtl })

let link: z.infer<typeof LinkSchema> | null = null

const getLink = async (key: string) =>
await KV.get(`link:${key}`, { type: 'json', cacheTtl: linkCacheTtl })

link = await getLink(slug)

// 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) {
event.context.link = link
try {
Expand Down