feat: init

This commit is contained in:
ccbikai
2024-05-25 08:09:30 +08:00
commit bd47e755d5
298 changed files with 21915 additions and 0 deletions
+40
View File
@@ -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' })
}
})
+27
View File
@@ -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 }
}
})
+15
View File
@@ -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}`)
}
})
+35
View File
@@ -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 }
}
})
+29
View File
@@ -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
})
+18
View File
@@ -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',
})
})
+19
View File
@@ -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)
})
+25
View File
@@ -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)
})
+29
View File
@@ -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)
})
+6
View File
@@ -0,0 +1,6 @@
export default eventHandler(() => {
return {
name: 'Sink',
url: 'https://sink.cool',
}
})