mirror of
https://github.com/Nezumi-2711/Sink.git
synced 2026-09-22 13:48:35 +00:00
feat: init
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { z } from 'zod'
|
||||
import { destr } from 'destr'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const url = (await getValidatedQuery(event, z.object({
|
||||
url: z.string().url(),
|
||||
}).parse)).url
|
||||
const { cloudflare } = event.context
|
||||
const { AI } = cloudflare.env
|
||||
|
||||
if (AI) {
|
||||
const { aiPrompt, aiModel } = useRuntimeConfig(event)
|
||||
const { slugRegex } = useAppConfig(event)
|
||||
const messages = [
|
||||
{ role: 'system', content: aiPrompt.replace('{slugRegex}', slugRegex.toString()) },
|
||||
|
||||
{ role: 'user', content: 'https://www.cloudflare.com/' },
|
||||
{ role: 'assistant', content: '{"slug": "cloudflare"}' },
|
||||
|
||||
{ role: 'user', content: 'https://github.com/nuxt-hub/' },
|
||||
{ role: 'assistant', content: '{"slug": "nuxt-hub"}' },
|
||||
|
||||
{ role: 'user', content: 'https://sink.cool/' },
|
||||
{ role: 'assistant', content: '{"slug": "sink-cool"}' },
|
||||
|
||||
{ role: 'user', content: 'https://github.com/ccbikai/sink' },
|
||||
{ role: 'assistant', content: '{"slug": "sink"}' },
|
||||
|
||||
{
|
||||
role: 'user',
|
||||
content: url,
|
||||
},
|
||||
]
|
||||
const { response } = await AI.run(aiModel, { messages })
|
||||
return destr(response)
|
||||
}
|
||||
else {
|
||||
throw createError({ status: 501, statusText: 'AI not enabled' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import { LinkSchema } from '@/schemas/link'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const link = await readValidatedBody(event, LinkSchema.parse)
|
||||
|
||||
const { cloudflare } = event.context
|
||||
const { KV } = cloudflare.env
|
||||
const existingLink = await KV.get(`link:${link.slug}`)
|
||||
if (existingLink) {
|
||||
throw createError({
|
||||
status: 409, // Conflict
|
||||
statusText: 'Link already exists',
|
||||
})
|
||||
}
|
||||
else {
|
||||
const expiration = getExpiration(event, link.expiration)
|
||||
|
||||
await KV.put(`link:${link.slug}`, JSON.stringify(link), {
|
||||
expiration,
|
||||
metadata: {
|
||||
expiration,
|
||||
},
|
||||
})
|
||||
setResponseStatus(event, 201)
|
||||
return { link }
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
export default eventHandler(async (event) => {
|
||||
const { previewMode } = useRuntimeConfig(event).public
|
||||
if (previewMode) {
|
||||
throw createError({
|
||||
status: 403,
|
||||
statusText: 'Preview mode cannot delete links.',
|
||||
})
|
||||
}
|
||||
const { slug } = await readBody(event)
|
||||
if (slug) {
|
||||
const { cloudflare } = event.context
|
||||
const { KV } = cloudflare.env
|
||||
await KV.delete(`link:${slug}`)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { z } from 'zod'
|
||||
import { LinkSchema } from '@/schemas/link'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const { previewMode } = useRuntimeConfig(event).public
|
||||
if (previewMode) {
|
||||
throw createError({
|
||||
status: 403,
|
||||
statusText: 'Preview mode cannot edit links.',
|
||||
})
|
||||
}
|
||||
const link = await readValidatedBody(event, LinkSchema.parse)
|
||||
const { cloudflare } = event.context
|
||||
const { KV } = cloudflare.env
|
||||
|
||||
const existingLink: z.infer<typeof LinkSchema> | null = await KV.get(`link:${link.slug}`, { type: 'json' })
|
||||
if (existingLink) {
|
||||
const newLink = {
|
||||
...existingLink,
|
||||
...link,
|
||||
id: existingLink.id, // don't update id
|
||||
createdAt: existingLink.createdAt, // don't update createdAt
|
||||
updatedAt: Math.floor(Date.now() / 1000),
|
||||
}
|
||||
const expiration = getExpiration(event, newLink.expiration)
|
||||
await KV.put(`link:${newLink.slug}`, JSON.stringify(newLink), {
|
||||
expiration,
|
||||
metadata: {
|
||||
expiration,
|
||||
},
|
||||
})
|
||||
setResponseStatus(event, 201)
|
||||
return { link: newLink }
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const { cloudflare } = event.context
|
||||
const { KV } = cloudflare.env
|
||||
const { limit, cursor } = await getValidatedQuery(event, z.object({
|
||||
limit: z.coerce.number().max(1024).default(20),
|
||||
cursor: z.string().trim().max(1024).optional(),
|
||||
}).parse)
|
||||
const list = await KV.list({
|
||||
prefix: `link:`,
|
||||
limit,
|
||||
cursor,
|
||||
})
|
||||
if (Array.isArray(list.keys)) {
|
||||
list.links = await Promise.all(list.keys.map(async (key: { name: string }) => {
|
||||
const { metadata, value: link } = await KV.getWithMetadata(key.name, { type: 'json' })
|
||||
if (link) {
|
||||
return {
|
||||
...metadata,
|
||||
...link,
|
||||
}
|
||||
}
|
||||
return link
|
||||
}))
|
||||
}
|
||||
delete list.keys
|
||||
return list
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
export default eventHandler(async (event) => {
|
||||
const slug = getQuery(event).slug
|
||||
if (slug) {
|
||||
const { cloudflare } = event.context
|
||||
const { KV } = cloudflare.env
|
||||
const { metadata, value: link } = await KV.getWithMetadata(`link:${slug}`, { type: 'json' })
|
||||
if (link) {
|
||||
return {
|
||||
...metadata,
|
||||
...link,
|
||||
}
|
||||
}
|
||||
}
|
||||
throw createError({
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { QuerySchema } from '@/schemas/query'
|
||||
|
||||
const { select } = SqlBricks
|
||||
|
||||
function query2sql(query: Query, event: H3Event): string {
|
||||
const filter = query2filter(query)
|
||||
const { dataset } = useRuntimeConfig(event)
|
||||
// visitors did not consider sampling
|
||||
const sql = select(`SUM(_sample_interval) as visits, COUNT(DISTINCT ${logsMap.ip}) as visitors, COUNT(DISTINCT ${logsMap.referer}) as referers`).from(dataset).where(filter)
|
||||
appendTimeFilter(sql, query)
|
||||
return sql.toString()
|
||||
}
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const query = await getValidatedQuery(event, QuerySchema.parse)
|
||||
const sql = query2sql(query, event)
|
||||
return useWAE(event, sql)
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { z } from 'zod'
|
||||
import { QuerySchema } from '@/schemas/query'
|
||||
|
||||
const { select } = SqlBricks
|
||||
|
||||
const MetricsQuerySchema = QuerySchema.extend({
|
||||
type: z.string(),
|
||||
})
|
||||
|
||||
function query2sql(query: z.infer<typeof MetricsQuerySchema>, event: H3Event): string {
|
||||
const filter = query2filter(query)
|
||||
const { dataset } = useRuntimeConfig(event)
|
||||
|
||||
// @ts-expect-error todo
|
||||
const sql = select(`${logsMap[query.type]} as name, SUM(_sample_interval) as count`).from(dataset).where(filter).groupBy('name').orderBy('count DESC').limit(query.limit)
|
||||
appendTimeFilter(sql, query)
|
||||
return sql.toString()
|
||||
}
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const query = await getValidatedQuery(event, MetricsQuerySchema.parse)
|
||||
const sql = query2sql(query, event)
|
||||
return useWAE(event, sql)
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { z } from 'zod'
|
||||
import { QuerySchema } from '@/schemas/query'
|
||||
|
||||
const { select } = SqlBricks
|
||||
|
||||
const unitMap: { [x: string]: string } = {
|
||||
hour: '%Y-%m-%d %H',
|
||||
day: '%Y-%m-%d',
|
||||
}
|
||||
|
||||
const ViewsQuerySchema = QuerySchema.extend({
|
||||
unit: z.string(),
|
||||
clientTimezone: z.string().default('Etc/UTC'),
|
||||
})
|
||||
|
||||
function query2sql(query: z.infer<typeof ViewsQuerySchema>, event: H3Event): string {
|
||||
const filter = query2filter(query)
|
||||
const { dataset } = useRuntimeConfig(event)
|
||||
const sql = select(`formatDateTime(timestamp, '${unitMap[query.unit]}', '${query.clientTimezone}') as time, SUM(_sample_interval) as visits, COUNT(DISTINCT ${logsMap.ip}) as visitors`).from(dataset).where(filter).groupBy('time').orderBy('time')
|
||||
appendTimeFilter(sql, query)
|
||||
return sql.toString()
|
||||
}
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const query = await getValidatedQuery(event, ViewsQuerySchema.parse)
|
||||
const sql = query2sql(query, event)
|
||||
return useWAE(event, sql)
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
export default eventHandler(() => {
|
||||
return {
|
||||
name: 'Sink',
|
||||
url: 'https://sink.cool',
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user