Skip to content

Commit 5206d89

Browse files
HyteqHyteq
authored andcommitted
Merge branch 'staging'
2 parents 597e27f + 31e0cd1 commit 5206d89

10 files changed

Lines changed: 287 additions & 201 deletions

File tree

apps/api/src/lib/website-utils.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { auth } from '@databuddy/auth';
2+
import { db, userPreferences, websites } from '@databuddy/db';
3+
import { cacheable } from '@databuddy/redis';
4+
import type { Website } from '@databuddy/shared';
5+
import { eq } from 'drizzle-orm';
6+
7+
export interface WebsiteContext {
8+
user: unknown;
9+
session: unknown;
10+
website?: Website;
11+
timezone: string;
12+
}
13+
14+
export interface WebsiteValidationResult {
15+
success: boolean;
16+
website?: Website;
17+
error?: string;
18+
}
19+
20+
const getCachedWebsite = cacheable(
21+
async (websiteId: string) => {
22+
try {
23+
const website = await db.query.websites.findFirst({
24+
where: eq(websites.id, websiteId),
25+
});
26+
return website || null;
27+
} catch {
28+
return null;
29+
}
30+
},
31+
{
32+
expireInSec: 300,
33+
prefix: 'website-cache',
34+
staleWhileRevalidate: true,
35+
staleTime: 60,
36+
}
37+
);
38+
39+
const getWebsiteDomain = cacheable(
40+
async (websiteId: string): Promise<string | null> => {
41+
try {
42+
const website = await db.query.websites.findFirst({
43+
where: eq(websites.id, websiteId),
44+
});
45+
return website?.domain || null;
46+
} catch {
47+
return null;
48+
}
49+
},
50+
{
51+
expireInSec: 300,
52+
prefix: 'website-domain',
53+
staleWhileRevalidate: true,
54+
staleTime: 60,
55+
}
56+
);
57+
58+
const getCachedWebsiteDomain = cacheable(
59+
async (websiteIds: string[]): Promise<Record<string, string | null>> => {
60+
const results: Record<string, string | null> = {};
61+
62+
await Promise.all(
63+
websiteIds.map(async (id) => {
64+
results[id] = await getWebsiteDomain(id);
65+
})
66+
);
67+
68+
return results;
69+
},
70+
{
71+
expireInSec: 300,
72+
prefix: 'website-domains-batch',
73+
staleWhileRevalidate: true,
74+
staleTime: 60,
75+
}
76+
);
77+
78+
export async function getTimezone(
79+
request: Request,
80+
session: { user?: { id: string } } | null
81+
): Promise<string> {
82+
const url = new URL(request.url);
83+
const headerTimezone = request.headers.get('x-timezone');
84+
const paramTimezone = url.searchParams.get('timezone');
85+
86+
if (session?.user) {
87+
const pref = await db.query.userPreferences.findFirst({
88+
where: eq(userPreferences.userId, session.user.id),
89+
});
90+
if (pref?.timezone && pref.timezone !== 'auto') {
91+
return pref.timezone;
92+
}
93+
}
94+
95+
return headerTimezone || paramTimezone || 'UTC';
96+
}
97+
98+
export async function deriveWebsiteContext({ request }: { request: Request }) {
99+
const session = await auth.api.getSession({
100+
headers: request.headers,
101+
});
102+
103+
const url = new URL(request.url);
104+
const website_id = url.searchParams.get('website_id');
105+
106+
if (!website_id) {
107+
if (!session?.user) {
108+
throw new Error('Unauthorized');
109+
}
110+
const timezone = await getTimezone(request, session);
111+
return { user: session.user, session, timezone };
112+
}
113+
114+
const website = await getCachedWebsite(website_id);
115+
116+
if (!website) {
117+
throw new Error('Website not found');
118+
}
119+
120+
if (website.isPublic) {
121+
const timezone = await getTimezone(request, null);
122+
return { user: null, session: null, website, timezone };
123+
}
124+
125+
if (!session?.user) {
126+
throw new Error('Unauthorized');
127+
}
128+
129+
const timezone = await getTimezone(request, session);
130+
return { user: session.user, session, website, timezone };
131+
}
132+
133+
export async function validateWebsite(
134+
websiteId: string
135+
): Promise<WebsiteValidationResult> {
136+
const website = await getCachedWebsite(websiteId);
137+
138+
if (!website) {
139+
return { success: false, error: 'Website not found' };
140+
}
141+
142+
return { success: true, website };
143+
}
144+
145+
export { getWebsiteDomain, getCachedWebsiteDomain, getCachedWebsite };

apps/api/src/middleware/logging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

apps/api/src/query/builders/summary.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,7 @@ export const SummaryBuilders: Record<string, SimpleQueryConfig> = {
283283
},
284284

285285
active_stats: {
286-
customSql: (websiteId: string, startDate?: string, endDate?: string) => {
287-
let timeCondition = '';
288-
if (startDate && endDate) {
289-
timeCondition =
290-
'time >= parseDateTimeBestEffort({startDate:String}) AND time <= parseDateTimeBestEffort({endDate:String})';
291-
} else {
292-
timeCondition = 'time >= now() - INTERVAL 5 MINUTE';
293-
}
286+
customSql: (websiteId: string) => {
294287
return {
295288
sql: `
296289
SELECT
@@ -300,11 +293,10 @@ export const SummaryBuilders: Record<string, SimpleQueryConfig> = {
300293
WHERE event_name = 'screen_view'
301294
AND client_id = {websiteId:String}
302295
AND session_id != ''
303-
AND ${timeCondition}
296+
AND time >= now() - INTERVAL 5 MINUTE
304297
`,
305298
params: {
306299
websiteId,
307-
...(startDate && endDate ? { startDate, endDate } : {}),
308300
},
309301
};
310302
},

apps/api/src/routes/assistant.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import { auth } from '@databuddy/auth';
2-
import { db, websites } from '@databuddy/db';
3-
import { cacheable } from '@databuddy/redis';
4-
import { eq } from 'drizzle-orm';
52
import { Elysia } from 'elysia';
63
import type { StreamingUpdate } from '../agent';
74
import {
@@ -10,6 +7,7 @@ import {
107
createStreamingResponse,
118
processAssistantRequest,
129
} from '../agent';
10+
import { validateWebsite } from '../lib/website-utils';
1311
import { createRateLimitMiddleware } from '../middleware/rate-limit';
1412
import { AssistantRequestSchema } from '../schemas';
1513

@@ -20,35 +18,6 @@ async function* createErrorResponse(
2018
yield { type: 'error', content: message };
2119
}
2220

23-
const getCachedWebsite = cacheable(
24-
async (websiteId: string) => {
25-
try {
26-
const website = await db.query.websites.findFirst({
27-
where: eq(websites.id, websiteId),
28-
});
29-
return website || null;
30-
} catch {
31-
return null;
32-
}
33-
},
34-
{
35-
expireInSec: 300,
36-
prefix: 'assistant-website',
37-
staleWhileRevalidate: true,
38-
staleTime: 60,
39-
}
40-
);
41-
42-
async function validateWebsite(websiteId: string) {
43-
const website = await getCachedWebsite(websiteId);
44-
45-
if (!website) {
46-
return { success: false, error: 'Website not found' };
47-
}
48-
49-
return { success: true, website };
50-
}
51-
5221
export const assistant = new Elysia({ prefix: '/v1/assistant' })
5322
.use(createRateLimitMiddleware({ type: 'expensive' }))
5423
.derive(async ({ request }) => {

apps/api/src/routes/query.ts

Lines changed: 6 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { auth } from '@databuddy/auth';
2-
import { db, userPreferences, websites } from '@databuddy/db';
3-
import { cacheable } from '@databuddy/redis';
4-
import { eq } from 'drizzle-orm';
51
import { Elysia, t } from 'elysia';
2+
import {
3+
deriveWebsiteContext,
4+
getCachedWebsiteDomain,
5+
getWebsiteDomain,
6+
} from '../lib/website-utils';
67
import { createRateLimitMiddleware } from '../middleware/rate-limit';
78
import { compileQuery, executeQuery } from '../query';
89
import { QueryBuilders } from '../query/builders';
@@ -23,66 +24,9 @@ interface QueryParams {
2324
timezone?: string;
2425
}
2526

26-
async function getTimezone(
27-
request: Request,
28-
session: { user?: { id: string } } | null
29-
) {
30-
const url = new URL(request.url);
31-
const headerTimezone = request.headers.get('x-timezone');
32-
const paramTimezone = url.searchParams.get('timezone');
33-
34-
if (session?.user) {
35-
const pref = await db.query.userPreferences.findFirst({
36-
where: eq(userPreferences.userId, session.user.id),
37-
});
38-
if (pref?.timezone && pref.timezone !== 'auto') {
39-
return pref.timezone;
40-
}
41-
}
42-
43-
return headerTimezone || paramTimezone || 'UTC';
44-
}
45-
46-
async function deriveContext({ request }: { request: Request }) {
47-
const session = await auth.api.getSession({
48-
headers: request.headers,
49-
});
50-
51-
const url = new URL(request.url);
52-
const website_id = url.searchParams.get('website_id');
53-
54-
if (!website_id) {
55-
if (!session?.user) {
56-
throw new Error('Unauthorized');
57-
}
58-
const timezone = await getTimezone(request, session);
59-
return { user: session.user, session, timezone };
60-
}
61-
62-
const website = await db.query.websites.findFirst({
63-
where: eq(websites.id, website_id),
64-
});
65-
66-
if (!website) {
67-
throw new Error('Website not found');
68-
}
69-
70-
if (website.isPublic) {
71-
const timezone = await getTimezone(request, null);
72-
return { user: null, session: null, website, timezone };
73-
}
74-
75-
if (!session?.user) {
76-
throw new Error('Unauthorized');
77-
}
78-
79-
const timezone = await getTimezone(request, session);
80-
return { user: session.user, session, website, timezone };
81-
}
82-
8327
export const query = new Elysia({ prefix: '/v1/query' })
8428
.use(createRateLimitMiddleware({ type: 'api' }))
85-
.derive(deriveContext)
29+
.derive(deriveWebsiteContext)
8630
.get('/types', () => ({
8731
success: true,
8832
types: Object.keys(QueryBuilders),
@@ -199,45 +143,6 @@ export const query = new Elysia({ prefix: '/v1/query' })
199143
}
200144
);
201145

202-
const getWebsiteDomain = cacheable(
203-
async (websiteId: string): Promise<string | null> => {
204-
try {
205-
const website = await db.query.websites.findFirst({
206-
where: eq(websites.id, websiteId),
207-
});
208-
return website?.domain || null;
209-
} catch {
210-
return null;
211-
}
212-
},
213-
{
214-
expireInSec: 300,
215-
prefix: 'website-domain',
216-
staleWhileRevalidate: true,
217-
staleTime: 60,
218-
}
219-
);
220-
221-
const getCachedWebsiteDomain = cacheable(
222-
async (websiteIds: string[]): Promise<Record<string, string | null>> => {
223-
const results: Record<string, string | null> = {};
224-
225-
await Promise.all(
226-
websiteIds.map(async (id) => {
227-
results[id] = await getWebsiteDomain(id);
228-
})
229-
);
230-
231-
return results;
232-
},
233-
{
234-
expireInSec: 300,
235-
prefix: 'website-domains-batch',
236-
staleWhileRevalidate: true,
237-
staleTime: 60,
238-
}
239-
);
240-
241146
async function executeDynamicQuery(
242147
request: DynamicQueryRequestType,
243148
queryParams: QueryParams,

apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,6 @@ function LiveUserIndicator({ websiteId }: { websiteId: string }) {
112112
return () => clearTimeout(timer);
113113
}, [count, prevCount]);
114114

115-
if (count <= 0) {
116-
return null;
117-
}
118-
119115
const getChangeColor = () => {
120116
if (change === 'up') {
121117
return 'text-green-500';

apps/docs/app/(home)/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import Hero from '@/components/landing/hero';
2828
// }
2929
// }
3030

31-
export default async function HomePage() {
31+
export default function HomePage() {
3232
// const stars = await getGitHubStars();
3333

3434
return (

apps/docs/components/landing/hero.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,6 @@ export default function Hero() {
1616

1717
return (
1818
<section className="relative min-h-screen overflow-hidden">
19-
{/* Startup Fame Badge */}
20-
<div className="relative z-20 flex justify-center pt-6">
21-
<a
22-
href="https://startupfa.me/s/databuddy?utm_source=www.databuddy.cc"
23-
rel="noopener noreferrer"
24-
target="_blank"
25-
>
26-
<img
27-
alt="Featured on Startup Fame"
28-
height={54}
29-
src="https://startupfa.me/badges/featured-badge.webp"
30-
width={171}
31-
/>
32-
</a>
33-
</div>
3419
{/* Cool Grid Background */}
3520
<div className="absolute inset-0 bg-background" />
3621
<div className="absolute inset-0 bg-[linear-gradient(rgba(var(--primary),0.03)_2px,transparent_2px),linear-gradient(90deg,rgba(var(--primary),0.03)_2px,transparent_2px)] bg-[size:60px_60px]" />

0 commit comments

Comments
 (0)