Compare commits

..

35 Commits

Author SHA1 Message Date
ccbikai
12e3e7c7bc 0.1.4 2024-08-20 21:25:30 +08:00
ccbikai
233d366d6e Merge branch 'dev' 2024-08-20 21:25:06 +08:00
ccbikai
05eb3bfca3 feat: enhance error handling for unauthorized API calls
Implements automatic token removal and redirection to login upon 401 error status, improving user experience and security.
2024-08-20 21:24:32 +08:00
ccbikai
d8c92aa7a1 Merge branch 'preview' into dev 2024-08-18 20:06:16 +08:00
ccbikai
f2a2e00ce6 feat: add XIcon to dashboard metrics 2024-08-18 20:05:33 +08:00
ccbikai
20eed15967 Merge branch 'dev' into preview 2024-08-18 19:43:36 +08:00
ccbikai
53a6b5d403 refactor: remove duplicate icon mappings for clarity
Eliminated redundant entries in iconMaps to streamline the mapping process and enhance readability. This consolidation reduces clutter and ensures a more efficient and maintainable codebase.
2024-08-18 19:43:19 +08:00
ccbikai
36e7962b83 chore: update dependencies 2024-08-18 19:38:04 +08:00
ccbikai
b116d4c007 feat: enable AI integration and update dependencies
- Activated AI functionality in Nuxt configuration
- Updated AI model to a newer version for enhanced performance
- Bumped @nuxthub/core to 0.7.3 for critical bug fixes and improvements
2024-08-18 19:01:14 +08:00
ccbikai
55783573ef Merge branch 'master' into dev 2024-08-18 18:40:09 +08:00
面条
ef3e704d79
Merge pull request #28 from fqd511/feature/improve-slug-readability
feat: improve slug readability
2024-07-30 20:12:47 +08:00
面条
5cf1e89ce4
Merge branch 'master' into feature/improve-slug-readability 2024-07-30 20:12:34 +08:00
ccbikai
6d0a67d9b5 feat: enhance nanoid character set for better readability
Adjusted the character set used by nanoid to exclude ambiguous characters, improving readability and reducing potential user confusion.
2024-07-28 13:40:25 +08:00
ccbikai
b2e859107d chore: Update Twitter handle to reflect new username
Update Twitter URL across multiple files to point to the new username, ensuring consistency and accuracy in social media links.
2024-07-28 13:39:32 +08:00
v
08ed8da522 feat: improve slug readability
(cherry picked from commit d950387f8cc87b166fd24bd75276bf34e1c5daa5)
2024-07-26 19:22:47 +08:00
ccbikai
2856c4013f 0.1.3 2024-07-21 20:51:29 +08:00
ccbikai
d7fce2eac0 Merge branch 'dev' 2024-07-21 20:51:17 +08:00
ccbikai
576eee43ca Merge branch 'dev' into preview 2024-07-20 20:23:37 +08:00
ccbikai
52187d1ff6 feat: disable query string redirection by default
Enhances security and performance by preventing query strings from being carried over during redirection, aligning with best practices.
2024-07-20 18:34:49 +08:00
ccbikai
2876385f20 feat: add link cache TTL for performance optimization
Improves response times by introducing a configurable link cache TTL, defaulting to 60 seconds, to ensure quick access to frequently requested links while maintaining responsiveness to updates.
2024-07-20 18:21:40 +08:00
ccbikai
3c0a7be6eb style: remove unnecessary newline for cleaner code 2024-07-20 18:13:05 +08:00
ccbikai
8f8865801a feat: redirect dashboard to analysis and update nav link
Enhances navigation by redirecting the main dashboard to the analysis page and updating the navigation link to directly access the analysis section. This change streamlines user access to the primary dashboard functionality.
2024-07-20 18:11:38 +08:00
ccbikai
7bcc5b27be Merge branch 'master' into dev 2024-07-20 18:04:24 +08:00
面条
6b3dd8d47e
Merge pull request #23 from dr-data/main
link first
2024-07-20 18:02:24 +08:00
dr-data
ca12fdd876 link first 2024-07-15 00:20:15 +08:00
dr-data
21d8352de0 link first 2024-07-15 00:16:45 +08:00
dr-data
09a97070d3 link first 2024-07-15 00:12:12 +08:00
ccbikai
0d10da9b04 Merge branch 'dev' 2024-07-11 19:55:42 +08:00
ccbikai
01be05c0fc chore: Update package dependencies and optimize imports 2024-07-08 20:28:38 +08:00
ccbikai
e16fb88c08 Merge branch 'master' into dev 2024-07-08 15:51:08 +08:00
ccbikai
bac9abb9a8 docs: enhance README with Hacker News feature badge
Added a new badge to the README to highlight that the project has been featured on Hacker News, increasing its visibility and credibility. Improved the formatting of the existing Trendshift badge.
2024-07-02 20:03:48 +08:00
ccbikai
de411396e2 chore: Update Twitter profile link 2024-07-01 13:30:08 +08:00
面条
528f5ddc48
docs: Add Badges 2024-06-27 19:52:25 +08:00
面条
503e62aa9e
chore: bump version 2024-06-14 18:56:48 +08:00
ccbikai
a98aa7c4e9 Merge branch 'master' into dev 2024-06-12 20:32:07 +08:00
25 changed files with 2881 additions and 3659 deletions

View File

@ -2,6 +2,8 @@ NUXT_PUBLIC_PREVIEW_MODE=true
NUXT_PUBLIC_SLUG_DEFAULT_LENGTH=5
NUXT_SITE_TOKEN=SinkCool
NUXT_REDIRECT_STATUS_CODE=308
NUXT_LINK_CACHE_TTL=60
NUXT_REDIRECT_WITH_QUERY=false
NUXT_HOME_URL="https://sink.cool"
NUXT_CF_ACCOUNT_ID=123456
NUXT_CF_API_TOKEN=CloudflareAPIToken

16
.github/FUNDING.yml vendored
View File

@ -1,14 +1,2 @@
# These are supported funding model platforms
github: ccbikai # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: ccbikai # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
github: ccbikai
buy_me_a_coffee: ccbikai

View File

@ -2,6 +2,30 @@
**A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.**
<a href="https://trendshift.io/repositories/10421" target="_blank">
<img
src="https://trendshift.io/api/badge/repositories/10421"
alt="ccbikai/Sink | Trendshift"
style="width: 250px; height: 55px;"
width="250"
height="55"
/>
</a>
<a href="https://news.ycombinator.com/item?id=40843683">
<img
src="https://hackernews-badge.vercel.app/api?id=40843683"
alt="Featured on Hacker News"
style="width: 250px; height: 55px;"
width="250"
height="55"
/>
</a>
![Cloudflare](https://img.shields.io/badge/Cloudflare-F69652?style=flat&logo=cloudflare&logoColor=white)
![Nuxt](https://img.shields.io/badge/Nuxt-00DC82?style=flat&logo=nuxtdotjs&logoColor=white)
![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-06B6D4?style=flat&logo=tailwindcss&logoColor=white)
![shadcn/ui](https://img.shields.io/badge/shadcn/ui-000000?style=flat&logo=shadcnui&logoColor=white)
![Hero](./public/image.png)
----
@ -89,5 +113,5 @@ We welcome your contributions and PRs.
## ☕ Sponsor
1. [Follow Me on X(Twitter)](https://x.com/ccbikai).
1. [Follow Me on X(Twitter)](https://x.com/0xKaiBi).
2. [Become a sponsor to on GitHub](https://github.com/sponsors/ccbikai).

View File

@ -1,7 +1,7 @@
<script setup>
const { title, description, image } = useAppConfig()
useSeoMeta({
title: title + ' - ' + description,
title: `${title} - ${description}`,
description,
ogType: 'website',
ogTitle: title,

View File

@ -1,5 +1,5 @@
<script setup>
import { Sun, Moon, Laptop } from 'lucide-vue-next'
import { Laptop, Moon, Sun } from 'lucide-vue-next'
const colorMode = useColorMode()
</script>

View File

@ -10,14 +10,14 @@ const route = useRoute()
@update:model-value="navigateTo"
>
<TabsList>
<TabsTrigger value="/dashboard">
Analysis
</TabsTrigger>
<TabsTrigger
value="/dashboard/links"
>
Links
</TabsTrigger>
<TabsTrigger value="/dashboard/analysis">
Analysis
</TabsTrigger>
</TabsList>
</Tabs>
<slot name="left" />

View File

@ -114,8 +114,13 @@ async function onSubmit(formData) {
body: link,
})
dialogOpen.value = false
emit('update:link', newLink, isEdit ? 'edit' : 'create')
isEdit ? toast('Link updated successfully') : toast('Link created successfully')
emit('update:link', newLink)
if (isEdit) {
toast('Link updated successfully')
}
else {
toast('Link created successfully')
}
}
const { previewMode } = useRuntimeConfig().public

View File

@ -1,26 +1,29 @@
<script setup>
// https://vue3-simple-icons.wyatt-herkamp.dev/
import {
AppleIcon,
AndroidIcon,
AppleIcon,
DebianIcon,
FacebookIcon,
FirefoxBrowserIcon,
GnuIcon,
GoogleChromeIcon,
GoogleIcon,
HuaweiIcon,
IOsIcon,
InternetExplorerIcon,
// InternetExplorerIcon,
LinuxIcon,
MacOsIcon,
MicrosoftEdgeIcon,
// MicrosoftEdgeIcon,
OperaIcon,
SafariIcon,
SamsungIcon,
UbuntuIcon,
VivoIcon,
WeChatIcon,
WindowsIcon,
WearOsIcon,
// WindowsIcon,
XIcon,
XiaomiIcon,
YandexCloudIcon,
} from 'vue3-simple-icons'
@ -47,6 +50,7 @@ defineProps({
const iconMaps = {
'android': AndroidIcon,
'android browser': AndroidIcon,
'browser': Globe,
'chrome': GoogleChromeIcon,
'chrome headless': GoogleChromeIcon,
@ -55,15 +59,15 @@ const iconMaps = {
'curl': Terminal,
'debian': DebianIcon,
'desktop': MonitorCheck,
'edge': MicrosoftEdgeIcon,
'facebook': FacebookIcon,
'facebookexternalhit': FacebookIcon,
'firefox': FirefoxBrowserIcon,
'googlebot': GoogleIcon,
'googlebot-image': GoogleIcon,
'gnu': GnuIcon,
'harmonyos': HuaweiIcon,
'huawei browser': HuaweiIcon,
'ie': InternetExplorerIcon,
// 'ie': InternetExplorerIcon,
'ios': IOsIcon,
'ipad': AppleIcon,
'iphone': AppleIcon,
@ -82,10 +86,11 @@ const iconMaps = {
'safari': SafariIcon,
'samsung internet': SamsungIcon,
'tablet': Tablet,
'twitterbot': XIcon,
'ubuntu': UbuntuIcon,
'vivo browser': VivoIcon,
'wechat': WeChatIcon,
'windows': WindowsIcon,
'wearable': WearOsIcon,
'yandex': YandexCloudIcon,
}
</script>

View File

@ -1,5 +1,5 @@
<script setup>
import { Link, AreaChart, ServerOff, Paintbrush, Sparkles, Hourglass } from 'lucide-vue-next'
import { AreaChart, Hourglass, Link, Paintbrush, ServerOff, Sparkles } from 'lucide-vue-next'
const features = ref([
{

View File

@ -5,7 +5,7 @@ import { ArrowRight } from 'lucide-vue-next'
<template>
<a
href="https://x.com/ccbikai"
href="https://x.com/0xKaiBi"
target="_blank"
title="X(Twitter)"
class="inline-flex items-center px-3 py-1 mx-auto my-4 space-x-1 text-sm font-medium rounded-lg bg-muted"

View File

@ -1,5 +1,5 @@
<script setup>
import { GmailIcon, TelegramIcon, BloggerIcon, XIcon, MastodonIcon, GitHubIcon } from 'vue3-simple-icons'
import { BloggerIcon, GitHubIcon, GmailIcon, MastodonIcon, TelegramIcon, XIcon } from 'vue3-simple-icons'
const email = ref(null)
onMounted(() => {
@ -61,7 +61,7 @@ onMounted(() => {
</a>
<a
href="https://x.com/ccbikai"
href="https://x.com/0xKaiBi"
target="_blank"
title="Twitter"
class="text-gray-400 hover:text-gray-500"

View File

@ -14,6 +14,14 @@ Sets the default length of the generated SLUG.
Redirects default to use HTTP 301 status code, you can set it to `302`/`307`/`308`.
## `NUXT_LINK_CACHE_TTL`
Cache links can speed up access, but setting them too long may result in slow changes taking effect. The default value is 60 seconds.
## `NUXT_REDIRECT_WITH_QUERY`
URL parameters are not carried during link redirection by default and it is not recommended to enable this feature.
## `NUXT_HOME_URL`
The default Sink homepage is the introduction page, you can replace it with your own website.

View File

@ -1,11 +1,11 @@
// @ts-check
// import antfu from '@antfu/eslint-config'
import antfu from '@antfu/eslint-config'
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
// antfu(),
antfu(),
{
ignores: ['components/ui'],
ignores: ['components/ui', '.data', 'public/world.json'],
},
{
rules: {

View File

@ -1,6 +1,7 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxthub/core',
'shadcn-nuxt',
@ -8,9 +9,11 @@ export default defineNuxtConfig({
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
],
colorMode: {
classSuffix: '',
},
routeRules: {
'/': {
prerender: true,
@ -18,38 +21,50 @@ export default defineNuxtConfig({
'/dashboard/**': {
ssr: false,
},
'/dashboard': {
redirect: '/dashboard/links',
},
},
hub: {
ai: true,
analytics: true,
blob: false,
cache: false,
database: false,
kv: true,
// ai: true,
},
eslint: {
config: {
stylistic: true,
standalone: false,
},
},
nitro: {
experimental: {
// Enable Server API documentation within NuxtHub
openAPI: true,
},
},
runtimeConfig: {
siteToken: 'SinkCool',
redirectStatusCode: '301',
linkCacheTtl: 60,
redirectWithQuery: false,
homeURL: '',
cfAccountId: '',
cfApiToken: '',
dataset: 'sink',
aiModel: '@cf/meta/llama-3-8b-instruct',
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"}`,
public: {
previewMode: '',
slugDefaultLength: '6',
},
},
compatibilityDate: '2024-07-08',
})

View File

@ -1,9 +1,12 @@
{
"name": "sink",
"type": "module",
"version": "0.1.1",
"version": "0.1.4",
"private": true,
"packageManager": "pnpm@9.1.2",
"packageManager": "pnpm@9.7.1",
"engines": {
"node": ">=20.11"
},
"scripts": {
"dev": "nuxt dev",
"build": "nuxt build",
@ -17,46 +20,43 @@
"wrangler": "wrangler",
"lint-staged": "lint-staged"
},
"engines": {
"node": ">=20.11"
},
"dependencies": {
"@unovis/ts": "^1.4.1",
"@unovis/vue": "^1.4.1",
"@vee-validate/zod": "^4.12.8",
"@vueuse/core": "^10.9.0",
"@unovis/ts": "^1.4.4",
"@unovis/vue": "^1.4.4",
"@vee-validate/zod": "^4.13.2",
"@vueuse/core": "^11.0.0",
"intl-parse-accept-language": "^1.0.0",
"lucide-vue-next": "^0.379.0",
"lucide-vue-next": "^0.428.0",
"mysql-bricks": "^1.1.2",
"nanoid": "^5.0.7",
"pluralize": "^8.0.0",
"qr-code-styling": "1.6.0-rc.1",
"radix-vue": "^1.8.1",
"radix-vue": "^1.9.4",
"ua-parser-js": "next",
"vee-validate": "^4.12.8",
"virtua": "^0.31.0",
"vue-sonner": "^1.1.2",
"vue3-simple-icons": "^11.13.0",
"vee-validate": "^4.13.2",
"virtua": "^0.33.7",
"vue-sonner": "^1.1.4",
"vue3-simple-icons": "^13.2.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@antfu/eslint-config": "^2.18.1",
"@nuxt/eslint": "^0.3.13",
"@nuxt/eslint-config": "^0.3.13",
"@nuxthub/core": "^0.5.17",
"@nuxtjs/color-mode": "^3.4.1",
"@nuxtjs/tailwindcss": "^6.12.0",
"@antfu/eslint-config": "^2.26.0",
"@nuxt/eslint": "^0.5.0",
"@nuxt/eslint-config": "^0.5.0",
"@nuxthub/core": "^0.7.3",
"@nuxtjs/color-mode": "^3.4.4",
"@nuxtjs/tailwindcss": "^6.12.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"eslint": "^8.57.0",
"lint-staged": "^15.2.4",
"nuxt": "^3.11.2",
"eslint": "^9.9.0",
"lint-staged": "^15.2.9",
"nuxt": "^3.12.4",
"shadcn-nuxt": "^0.10.4",
"simple-git-hooks": "^2.11.1",
"tailwind-merge": "^2.3.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"vue-tsc": "^2.0.19",
"wrangler": "^3.57.1"
"vue-tsc": "^2.0.29",
"wrangler": "^3.72.0"
},
"simple-git-hooks": {
"pre-commit": "npm run lint-staged"

View File

@ -17,10 +17,11 @@ async function getLink() {
}
function updateLink(link, type) {
if (type === 'delete')
if (type === 'delete') {
navigateTo('/dashboard/links', {
replace: true,
})
}
}
onMounted(() => {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ const { slugRegex } = useAppConfig()
const slugDefaultLength = +useRuntimeConfig().public.slugDefaultLength
export const nanoid = (length: number = slugDefaultLength) => customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', length)
export const nanoid = (length: number = slugDefaultLength) => customAlphabet('23456789abcdefghjkmnpqrstuvwxyz', length)
export const LinkSchema = z.object({
id: z.string().trim().max(26).default(nanoid(10)),

View File

@ -1,5 +1,5 @@
import { writeFileSync } from 'fs'
import { join } from 'path'
import { writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { WorldMapSimplestTopoJSON } from '@unovis/ts/maps.js'
writeFileSync(join(import.meta.dirname, '../public/world.json'), JSON.stringify(WorldMapSimplestTopoJSON), 'utf8')

View File

@ -31,7 +31,8 @@ export default eventHandler(async (event) => {
content: url,
},
]
const { response } = await AI.run(aiModel, { messages })
// @ts-expect-error Workers AI is not typed
const { response } = await hubAI().run(aiModel, { messages })
return destr(response)
}
else {

View File

@ -12,6 +12,7 @@ export default eventHandler(async (event) => {
statusText: 'Link already exists',
})
}
else {
const expiration = getExpiration(event, link.expiration)

View File

@ -1,11 +1,11 @@
import type { z } from 'zod'
import { parsePath } from 'ufo'
import { parsePath, withQuery } from 'ufo'
import type { LinkSchema } from '@/schemas/link'
export default eventHandler(async (event) => {
const { pathname: slug } = parsePath(event.path.slice(1)) // remove leading slash
const { slugRegex, reserveSlug } = useAppConfig(event)
const { homeURL } = useRuntimeConfig(event)
const { homeURL, linkCacheTtl, redirectWithQuery } = useRuntimeConfig(event)
const { cloudflare } = event.context
if (event.path === '/' && homeURL)
@ -13,7 +13,7 @@ export default eventHandler(async (event) => {
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' })
const link: z.infer<typeof LinkSchema> | null = await KV.get(`link:${slug}`, { type: 'json', cacheTtl: linkCacheTtl })
if (link) {
event.context.link = link
try {
@ -22,7 +22,8 @@ export default eventHandler(async (event) => {
catch (error) {
console.error('Failed write access log:', error)
}
return sendRedirect(event, link.url, +useRuntimeConfig(event).redirectStatusCode)
const target = redirectWithQuery ? withQuery(link.url, getQuery(event)) : link.url
return sendRedirect(event, target, +useRuntimeConfig(event).redirectStatusCode)
}
}
})

View File

@ -2,11 +2,12 @@ import type { H3Event } from 'h3'
import { parseURL } from 'ufo'
import { UAParser } from 'ua-parser-js'
import {
Apps,
Bots,
CLIs,
Crawlers,
Emails,
ExtraDevices,
Fetchers,
InApps,
MediaPlayers,
Modules,
} from 'ua-parser-js/extensions'
@ -68,7 +69,7 @@ export function useAccessLog(event: H3Event) {
const userAgent = getHeader(event, 'user-agent') || ''
const uaInfo = (new UAParser(userAgent, {
browser: [Apps.browser || [], Bots.browser || [], CLIs.browser || [], Emails.browser || [], MediaPlayers.browser || [], Modules.browser || []].flat(),
browser: [Crawlers.browser || [], CLIs.browser || [], Emails.browser || [], Fetchers.browser || [], InApps.browser || [], MediaPlayers.browser || [], Modules.browser || []].flat(),
device: [ExtraDevices.device || []].flat(),
})).getResult()
@ -90,7 +91,6 @@ export function useAccessLog(event: H3Event) {
language,
os: uaInfo?.os?.name,
browser: uaInfo?.browser?.name,
// @ts-expect-error todo
browserType: uaInfo?.browser?.type,
device: uaInfo?.device?.model,
deviceType: uaInfo?.device?.type,

View File

@ -7,6 +7,10 @@ export function useAPI(api: string, options?: object): Promise<unknown> {
Authorization: `Bearer ${localStorage.getItem('SinkSiteToken') || ''}`,
},
})).catch((error) => {
if (error?.status === 401) {
localStorage.removeItem('SinkSiteToken')
navigateTo('/dashboard/login')
}
if (error?.data?.statusMessage) {
toast(error?.data?.statusMessage)
}