added visitor by country stat and visitor by city

pull/3/head
TZGyn 2 years ago
parent 300596c6b1
commit 092d18bbe4
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

Binary file not shown.

@ -0,0 +1 @@
ALTER TABLE "visitor" ADD COLUMN "city" varchar(255) NOT NULL;

@ -0,0 +1,187 @@
{
"id": "8f8ae49c-43bf-4e07-a30d-383e2b767524",
"prevId": "548a0489-b083-464f-800c-4dee6f1ff161",
"version": "5",
"dialect": "pg",
"tables": {
"session": {
"name": "session",
"schema": "",
"columns": {
"token": {
"name": "token",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"shortener": {
"name": "shortener",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"link": {
"name": "link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"visitor": {
"name": "visitor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"shortener_id": {
"name": "shortener_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"country_code": {
"name": "country_code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

@ -15,6 +15,13 @@
"when": 1700134783172,
"tag": "0001_regular_microchip",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1700882455122,
"tag": "0002_robust_the_executioner",
"breakpoints": true
}
]
}

@ -0,0 +1,18 @@
import { Tabs as TabsPrimitive } from "bits-ui";
import Content from "./tabs-content.svelte";
import List from "./tabs-list.svelte";
import Trigger from "./tabs-trigger.svelte";
const Root = TabsPrimitive.Root;
export {
Root,
Content,
List,
Trigger,
//
Root as Tabs,
Content as TabsContent,
List as TabsList,
Trigger as TabsTrigger
};

@ -0,0 +1,21 @@
<script lang="ts">
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = TabsPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export { className as class };
</script>
<TabsPrimitive.Content
class={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{value}
{...$$restProps}
>
<slot />
</TabsPrimitive.Content>

@ -0,0 +1,19 @@
<script lang="ts">
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = TabsPrimitive.ListProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<TabsPrimitive.List
class={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...$$restProps}
>
<slot />
</TabsPrimitive.List>

@ -0,0 +1,23 @@
<script lang="ts">
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = TabsPrimitive.TriggerProps;
type $$Events = TabsPrimitive.TriggerEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export { className as class };
</script>
<TabsPrimitive.Trigger
class={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{value}
{...$$restProps}
on:click
>
<slot />
</TabsPrimitive.Trigger>

@ -51,6 +51,7 @@ export const visitor = pgTable('visitor', {
length: 255,
}).notNull(),
country: varchar('country', { length: 255 }).notNull(),
city: varchar('city', { length: 255 }).notNull(),
})
export const visitorRelations = relations(visitor, ({ one }) => ({

@ -19,6 +19,10 @@ export const load = (async (event) => {
},
})
if (!shortener) {
throw redirect(303, '/')
}
const visitor = await db
.select({
count: sql<number>`cast(count(*) as int)`,
@ -27,9 +31,28 @@ export const load = (async (event) => {
.from(visitorSchema)
.groupBy(sql`to_char(${visitorSchema.createdAt}, 'MM')`)
if (!shortener) {
throw redirect(303, '/')
}
const visitorByCountry = await db
.select({
count: sql<number>`cast(count(*) as int)`,
country: visitorSchema.country,
code: visitorSchema.countryCode,
})
.from(visitorSchema)
.groupBy(visitorSchema.country, visitorSchema.countryCode)
const visitorByCity = await db
.select({
count: sql<number>`cast(count(*) as int)`,
country: visitorSchema.country,
code: visitorSchema.countryCode,
city: visitorSchema.city,
})
.from(visitorSchema)
.groupBy(
visitorSchema.country,
visitorSchema.countryCode,
visitorSchema.city,
)
return { shortener, visitor }
return { shortener, visitor, visitorByCountry, visitorByCity }
}) satisfies PageServerLoad

@ -2,6 +2,7 @@
import type { PageData } from './$types'
import { Separator } from '$lib/components/ui/separator'
import * as Card from '$lib/components/ui/card'
import * as Tabs from '$lib/components/ui/tabs'
import type { ApexOptions } from 'apexcharts'
import { mode } from 'mode-watcher'
import { onMount } from 'svelte'
@ -84,15 +85,15 @@
if (container) {
container.innerHTML = ''
}
var chart = new apexChart(container, options)
var chart = new ApexChart(container, options)
chart.render()
}
$: container && apexChart && renderChart(options)
$: container && ApexChart && renderChart(options)
let apexChart: typeof ApexCharts
let ApexChart: typeof ApexCharts
onMount(async () => {
apexChart = (await import('apexcharts')).default
ApexChart = (await import('apexcharts')).default
})
</script>
@ -112,4 +113,48 @@
<div bind:this={container}></div>
</Card.Content>
</Card.Root>
<Card.Root class="max-w-[500px]">
<Tabs.Root value="country">
<Card.Header
class="flex w-full flex-row items-center justify-between space-y-0">
<div>
<Card.Title>Visitors</Card.Title>
<Card.Description
>Visitors by Country/City</Card.Description>
</div>
<Tabs.List>
<Tabs.Trigger value="country">Country</Tabs.Trigger>
<Tabs.Trigger value="city">City</Tabs.Trigger>
</Tabs.List>
</Card.Header>
<Card.Content>
<Tabs.Content value="country">
{#each data.visitorByCountry as visitorByCountry}
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<img
src={`https://flagsapi.com/${visitorByCountry.code}/flat/64.png`}
alt="" />
<div>{visitorByCountry.country}</div>
</div>
<div>{visitorByCountry.count}</div>
</div>
{/each}
</Tabs.Content>
<Tabs.Content value="city">
{#each data.visitorByCity as visitorByCity}
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<img
src={`https://flagsapi.com/${visitorByCity.code}/flat/64.png`}
alt="" />
<div>{visitorByCity.city}</div>
</div>
<div>{visitorByCity.count}</div>
</div>
{/each}
</Tabs.Content>
</Card.Content>
</Tabs.Root>
</Card.Root>
</div>

@ -31,6 +31,7 @@ app.get(
country: geolocation.data.location.country.name as string,
country_code: geolocation.data.location.country
.alpha2 as string,
city: geolocation.data.location.city.name as string,
}
await db.insertInto('visitor').values(visitor_data).execute()

@ -30,6 +30,7 @@ export interface VisitorTable {
shortener_id: number
country: string
country_code: string
city: string
created_at: ColumnType<Date, string | undefined, never>
}

Loading…
Cancel
Save