Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import fastifyStatic from '@fastify/static';
import path from 'path';
import { fileURLToPath } from 'url';

declare module 'fastify' {
interface FastifyInstance {
authenticate: any;
}
}
Comment on lines +11 to +15
Comment thread
parthpatidar03 marked this conversation as resolved.


import { prismaPlugin } from './plugins/prisma.js';
import { redisPlugin } from './plugins/redis.js';
import { authRoutes } from './routes/auth.js';
Expand Down Expand Up @@ -60,6 +67,9 @@ export async function buildApp() {
// ─── Auth Decorator ───
app.decorate('authenticate', async function (request: any, reply: any) {
try {
if (!request.headers.authorization && request.cookies && request.cookies.token) {
request.headers.authorization = `Bearer ${request.cookies.token}`;
}
await request.jwtVerify();
} catch (err) {
reply.status(401).send({ error: 'Unauthorized' });
Expand Down
28 changes: 26 additions & 2 deletions apps/backend/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export async function authRoutes(app: FastifyInstance) {

return reply.redirect(`${process.env.PUBLIC_APP_URL}/dashboard`);
} catch (err) {
app.log.error('GitHub auth error:', err);
app.log.error(err as any, 'GitHub auth error');
return reply.status(500).send({ error: 'Authentication failed' });
}
});
Expand Down Expand Up @@ -235,7 +235,7 @@ export async function authRoutes(app: FastifyInstance) {

return reply.redirect(`${process.env.PUBLIC_APP_URL}/dashboard`);
} catch (err) {
app.log.error('Google auth error:', err);
app.log.error(err as any, 'Google auth error');
return reply.status(500).send({ error: 'Authentication failed' });
}
});
Expand Down Expand Up @@ -284,6 +284,30 @@ export async function authRoutes(app: FastifyInstance) {
reply.clearCookie('token', { path: '/' });
return { message: 'Logged out' };
});

// ─── Dev Login Bypass ───
if (process.env.NODE_ENV !== 'production') {
app.get('/dev-login', async (request: FastifyRequest, reply: FastifyReply) => {
const user = await app.prisma.user.findUnique({
where: { username: 'devcard-demo' },
});
if (!user) {
return reply.status(404).send({ error: 'Demo user not found' });
}
const token = app.jwt.sign(
{ id: user.id, username: user.username },
{ expiresIn: '30d' }
);
reply.setCookie('token', token, {
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
maxAge: 30 * 24 * 60 * 60,
});
return reply.redirect(`${process.env.PUBLIC_APP_URL || 'http://localhost:5173'}/devcard/analytics`);
});
}
}

function generateState(): string {
Expand Down
7 changes: 4 additions & 3 deletions apps/backend/src/routes/connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { encrypt } from '../utils/encryption.js';

const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
Expand Down Expand Up @@ -81,12 +82,12 @@ export async function connectRoutes(app: FastifyInstance) {
const tokenData = (await tokenRes.json()) as any;

if (tokenData.error) {
app.log.error('GitHub connect token error:', tokenData);
app.log.error(tokenData, 'GitHub connect token error');
return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=connect_failed`);
}

// Encrypt and store the token
const encryptedToken = app.encryption.encrypt(tokenData.access_token);
const encryptedToken = encrypt(tokenData.access_token);
Comment on lines 2 to +90
Comment thread
parthpatidar03 marked this conversation as resolved.

await app.prisma.oAuthToken.upsert({
where: {
Expand Down Expand Up @@ -116,7 +117,7 @@ export async function connectRoutes(app: FastifyInstance) {
return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?connected=github`);

} catch (err) {
app.log.error('GitHub connect error:', err);
app.log.error(err as any, 'GitHub connect error');
return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=server_error`);
}
});
Expand Down
5 changes: 5 additions & 0 deletions apps/web/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
autoprefixer: {}
}
};
Comment thread
parthpatidar03 marked this conversation as resolved.
89 changes: 89 additions & 0 deletions apps/web/src/routes/devcard/analytics/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { dev } from '$app/environment';

const API_BASE = process.env.BACKEND_URL || 'http://localhost:3000';

export interface AnalyticsOverview {
totalViews: number;
viewsToday: number;
uniqueViewers: number;
totalFollows: number;
recentViews: Array<{
createdAt: string;
source: string;
viewer?: {
avatarUrl?: string;
displayName?: string;
};
}>;
}

export interface AnalyticsViews {
meta: {
total: number;
};
data: Array<{
createdAt: string;
source: string;
viewerIp?: string;
viewer?: {
displayName?: string;
username?: string;
};
card?: {
title?: string;
};
}>;
}

export const load: PageServerLoad = async ({ fetch, cookies }) => {
const token = cookies.get('token');

if (!token) {
if (!dev) {
throw redirect(302, '/');
}
return {
overview: null,
views: null,
error: 'Please log in to view analytics'
};
}

try {
const headers = { Authorization: `Bearer ${token}` };
const [overviewRes, viewsRes] = await Promise.all([
fetch(`${API_BASE}/api/analytics/overview`, { headers }),
fetch(`${API_BASE}/api/analytics/views`, { headers })
Comment thread
parthpatidar03 marked this conversation as resolved.
]);

if (!overviewRes.ok || !viewsRes.ok) {
if (overviewRes.status === 401 || viewsRes.status === 401) {
if (!dev) {
throw redirect(302, '/');
}
}
return {
overview: null,
views: null,
error: 'Please log in to view analytics'
};
}
Comment thread
parthpatidar03 marked this conversation as resolved.
Comment thread
parthpatidar03 marked this conversation as resolved.

const overview = (await overviewRes.json()) as AnalyticsOverview;
const views = (await viewsRes.json()) as AnalyticsViews;

return { overview, views, error: null };
} catch (err) {
// If it's a redirect thrown by SvelteKit, let it bubble up
if (err && typeof err === 'object' && 'status' in err && 'location' in err) {
throw err;
}
return {
overview: null,
views: null,
error: 'Analytics service unavailable'
};
}
};
Loading