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
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/metabase_manager?schema=public
SENTRY_DSN=https://b693e785a5decfdf30a376c5f3837e95@o371117.ingest.sentry.io/4505657708249088
SENTRY_DSN=https://b693e785a5decfdf30a376c5f3837e95@o371117.ingest.sentry.io/4505657708249088
SUPER_USER_PASSWORD=superuserpassword123
31 changes: 31 additions & 0 deletions app/[dashboard]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { notFound } from 'next/navigation';

const dashboardLinks: { [key: string]: string } = {
manipur: 'https://mn-data.10bedicu.in/public/dashboard/a2b651c2-51b1-4948-b84c-889e2a5e9dba?date_range=&hub=&hospital_name=',
assam: 'https://caredata.assam.gov.in/public/dashboard/31925fb9-29a8-40ec-b210-92bc2577af11?date_range=&hub=&hospital_name=',
karnataka: 'https://caredata.karnataka.care/public/dashboard/2b2bd976-e234-44d0-8114-7d5684d891f9?date_range=&hub=&hospital_name=',
meghalaya: 'https://caredata.meghealth.gov.in/public/dashboard/3eb879a2-92d2-4899-b9e8-2c5caeb766a5?date_range=&hub=&hospital_name=',
sikkim: 'https://sk-data.10bedicu.in/public/dashboard/ca658e64-cc0d-405c-90b6-b0fecf23e06e?date_range=&hub=&hospital_name=',
nagaland: 'https://caredata.nagaland.gov.in/public/dashboard/04ffb6eb-4d28-4dab-8842-68901fd0876f?date_range=&hub=&hospital_name='
};

export default function DashboardPage({ params }: { params: { dashboard: string } }) {
const dashboardLink = dashboardLinks[params.dashboard];

if (!dashboardLink) {
notFound(); // Display a 404 page if the dashboard does not exist
}

return (
<div style={{ height: '100vh' }}>
<iframe
src={dashboardLink}
width="100%"
height="100%"
frameBorder="0"
allowFullScreen
title={`${params.dashboard} dashboard`}
/>
</div>
);
}
41 changes: 41 additions & 0 deletions app/api/auth/signin/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
import bcrypt from 'bcrypt';

export async function POST(request: Request) {
try {
const { username, password } = await request.json();

// Find the user by username
const user = await prisma.user.findUnique({
where: { username },
});

if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}

// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
}

// Return user details with id, username, and role
return NextResponse.json({
message: 'Sign-in successful',
user: {
id: user.id, // Include id here
username: user.username,
role: user.role,
},
});
} catch (error) {
console.error('Sign-in error:', error);
return NextResponse.json(
{ error: 'An error occurred during sign-in' },
{ status: 500 }
);
}
}
136 changes: 136 additions & 0 deletions app/api/execute-query/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// /app/api/execute-query/route.ts

import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';

export async function POST(request: Request) {
try {
const { query, serverDatabaseSelections, userId } = await request.json();

// Validate input data
if (
!query ||
!serverDatabaseSelections ||
!Array.isArray(serverDatabaseSelections) ||
userId === undefined
) {
return NextResponse.json(
{ error: 'Invalid request data' },
{ status: 400 }
);
}

const results = [];

for (const selection of serverDatabaseSelections) {
const { serverId, databaseId } = selection;

// Fetch user-specific server credentials
const userServer = await prisma.userMetabaseServer.findUnique({
where: {
userId_serverId: { userId, serverId },
},
include: {
server: true,
},
});

if (!userServer) {
results.push({
serverId,
serverUrl: '',
data: null,
error: 'Server not found for this user',
});
continue;
}

const server = {
hostUrl: userServer.server.hostUrl,
email: userServer.email,
password: userServer.password,
isSource: userServer.isSource, // User-specific isSource
};

try {
// Authenticate with Metabase API
const authResponse = await fetch(`${server.hostUrl}/api/session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: server.email,
password: server.password,
}),
});

if (!authResponse.ok) {
const errorData = await authResponse.json();
console.error(`Authentication error on server ${server.hostUrl}:`, errorData);
throw new Error(errorData.message || 'Authentication failed');
}

const authData = await authResponse.json();
const token = authData.id;

// Execute the query without pagination
const queryBody = {
database: databaseId,
type: 'native',
native: {
query: query,
},
};

console.log(`Executing query on server ${server.hostUrl} with database ID ${databaseId}:`, queryBody);

const queryResponse = await fetch(`${server.hostUrl}/api/dataset`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Metabase-Session': token,
},
body: JSON.stringify(queryBody),
});

if (!queryResponse.ok) {
const errorData = await queryResponse.json();
console.error(`Query execution error on server ${server.hostUrl}:`, errorData);
throw new Error(errorData.message || errorData.error || 'Query execution failed');
}

const queryData = await queryResponse.json();

if (!queryData.data || !queryData.data.rows || !queryData.data.cols) {
throw new Error('Invalid response format from Metabase');
}

results.push({
serverId: serverId,
serverUrl: server.hostUrl,
data: {
cols: queryData.data.cols,
rows: queryData.data.rows,
},
});
} catch (error: any) {
console.error(`Error querying server ${server.hostUrl}:`, error);
results.push({
serverId: serverId,
serverUrl: server.hostUrl,
data: null,
error: error.message,
});
}
}

return NextResponse.json(results, { status: 200 });
} catch (error: any) {
console.error('Error executing queries:', error);
return NextResponse.json(
{ error: 'Failed to execute queries' },
{ status: 500 }
);
}
}
104 changes: 104 additions & 0 deletions app/api/get-databases/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// /app/api/get-databases/route.ts

import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';

export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const serverIdParam = searchParams.get('serverId');
const userIdParam = searchParams.get('userId');

const serverId = serverIdParam ? parseInt(serverIdParam) : null;
const userId = userIdParam ? parseInt(userIdParam) : null;

if (!serverId) {
return NextResponse.json(
{ error: 'Server ID is required' },
{ status: 400 }
);
}

if (!userId) {
return NextResponse.json(
{ error: 'User ID is required' },
{ status: 400 }
);
}

// Fetch server credentials based on userId and serverId
const userServer = await prisma.userMetabaseServer.findUnique({
where: {
userId_serverId: { userId, serverId },
},
include: {
server: true,
},
});

if (!userServer) {
console.error(`No userServer found for userId: ${userId}, serverId: ${serverId}`);
return NextResponse.json({ error: 'Server not found' }, { status: 404 });
}

const server = {
id: userServer.server.id,
hostUrl: userServer.server.hostUrl,
email: userServer.email,
password: userServer.password,
};

console.log(`Attempting to authenticate with server ${server.hostUrl}`);

// Authenticate with Metabase API
const authResponse = await fetch(`${server.hostUrl}/api/session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: server.email,
password: server.password,
}),
});

const authResponseText = await authResponse.text();

if (!authResponse.ok) {
console.error(`Authentication failed for server ${server.hostUrl}:`, authResponseText);
throw new Error(`Authentication failed: ${authResponseText}`);
}

const authData = JSON.parse(authResponseText);
const token = authData.id;

console.log(`Authenticated with server ${server.hostUrl}, token: ${token}`);

// Fetch databases
const databasesResponse = await fetch(`${server.hostUrl}/api/database`, {
headers: {
'Content-Type': 'application/json',
'X-Metabase-Session': token,
},
});

const databasesResponseText = await databasesResponse.text();

if (!databasesResponse.ok) {
console.error(`Failed to fetch databases from server ${server.hostUrl}:`, databasesResponseText);
throw new Error(`Failed to fetch databases: ${databasesResponseText}`);
}

const databasesData = JSON.parse(databasesResponseText);

console.log(`Databases fetched from server ${server.hostUrl}:`, databasesData.data);

return NextResponse.json(databasesData.data, { status: 200 });
} catch (error: any) {
console.error('Error fetching databases:', error.message || error);
return NextResponse.json(
{ error: error.message || 'Failed to fetch databases' },
{ status: 500 }
);
}
}
4 changes: 4 additions & 0 deletions app/api/log.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Changes Made/Files/Routes Added:
1. auth/signin - route file for signing in using simple prisma stored username and password
2. servers - route file set up for GET, POST, DELETE requests for metabase servers for a given signed in user (passwords currently saved without hashing due to need)
3. users - route file set up for POST request for user creation (only available for superuser role)
Loading