Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ PUBLIC_PICTIQUE_BASE_URL=your_public_pictique_base_url_here
# Blabsy Configuration
PUBLIC_BLABSY_BASE_URL=your_public_blabsy_base_url_here

# Eid Wallet Configuration
# Eid Wallet & Pictique Configuration (Svelte)
PUBLIC_REGISTRY_URL=your_public_registry_url_here
PUBLIC_PROVISIONER_URL=your_public_provisioner_url_here

# Next.js Applications Configuration (eVoting, Blabsy, Group Charter Manager)
NEXT_PUBLIC_REGISTRY_URL=your_public_registry_url_here

# Neo4j Configuration
NEO4J_URI=bolt://neo4j:7687
NEO4J_USER=neo4j
Expand Down
4 changes: 4 additions & 0 deletions platforms/blabsy/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Registry URL for fetching motd and other registry services
# Note: In Next.js, environment variables exposed to the browser must be prefixed with NEXT_PUBLIC_
NEXT_PUBLIC_REGISTRY_URL=http://localhost:4321

36 changes: 36 additions & 0 deletions platforms/blabsy/src/components/common/maintenance-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react';
import axios from 'axios';

interface Motd {
status: 'up' | 'maintenance';
message: string;
}
Comment on lines +4 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract shared type definition to prevent drift.

The Motd interface is duplicated across three platforms (blabsy, eVoting, group-charter-manager). This creates a maintenance burden and risk of type inconsistencies.

Consider creating a shared types package (e.g., packages/types/src/motd.ts) and importing from there:

// packages/types/src/motd.ts
export interface Motd {
    status: 'up' | 'maintenance';
    message: string;
}

Then import in each platform:

-interface Motd {
-    status: 'up' | 'maintenance';
-    message: string;
-}
+import type { Motd } from '@repo/types';
🤖 Prompt for AI Agents
In platforms/blabsy/src/components/common/maintenance-banner.tsx around lines 4
to 7, the Motd interface is duplicated across multiple platforms; extract it to
a shared types package (e.g., packages/types/src/motd.ts) that exports the Motd
interface, update this file to import Motd from that package instead of
declaring it locally, and update the other platforms (eVoting,
group-charter-manager) to import the same shared type to prevent drift and
ensure a single source of truth.


export function MaintenanceBanner(): JSX.Element | null {
const [motd, setMotd] = useState<Motd | null>(null);

useEffect(() => {
const fetchMotd = async () => {
try {
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
} catch (error) {
console.error('Failed to fetch motd:', error);
}
};

void fetchMotd();
}, []);
Comment on lines +12 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add timeout and consider periodic refetching.

The axios request lacks a timeout configuration, which could cause the request to hang indefinitely if the registry is unresponsive. Additionally, the MOTD is fetched only once on mount, so users won't see status updates without a page refresh.

Apply these improvements:

 useEffect(() => {
     const fetchMotd = async () => {
         try {
             const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
-            const response = await axios.get<Motd>(`${registryUrl}/motd`);
+            const response = await axios.get<Motd>(`${registryUrl}/motd`, {
+                timeout: 5000, // 5 second timeout
+            });
             setMotd(response.data);
         } catch (error) {
             console.error('Failed to fetch motd:', error);
         }
     };
 
     void fetchMotd();
+    
+    // Optionally refetch every 5 minutes to catch status changes
+    const interval = setInterval(() => void fetchMotd(), 5 * 60 * 1000);
+    return () => clearInterval(interval);
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const fetchMotd = async () => {
try {
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
} catch (error) {
console.error('Failed to fetch motd:', error);
}
};
void fetchMotd();
}, []);
useEffect(() => {
const fetchMotd = async () => {
try {
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get<Motd>(`${registryUrl}/motd`, {
timeout: 5000, // 5 second timeout
});
setMotd(response.data);
} catch (error) {
console.error('Failed to fetch motd:', error);
}
};
void fetchMotd();
// Optionally refetch every 5 minutes to catch status changes
const interval = setInterval(() => void fetchMotd(), 5 * 60 * 1000);
return () => clearInterval(interval);
}, []);
🤖 Prompt for AI Agents
In platforms/blabsy/src/components/common/maintenance-banner.tsx around lines 12
to 24, the axios GET has no timeout and the MOTD is fetched only once; update
the fetch to include a request timeout (e.g., axios timeout or AbortController)
and implement periodic refetching using setInterval (choose a sensible interval
like 30–60s), ensure you handle request cancellation on unmount/interval reset
to avoid leaks, and keep error handling intact while updating state only when
the component is still mounted.


if (motd?.status !== 'maintenance') {
return null;
}

return (
<div className='bg-yellow-500 px-4 py-3 text-center text-sm font-medium text-black'>
⚠️ {motd.message}
</div>
);
}
Comment on lines +1 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Eliminate code duplication across platforms.

The MaintenanceBanner component is nearly identical across three platforms (blabsy, eVoting, group-charter-manager), with only minor styling and directive differences. This creates a significant maintenance burden.

Consider one of these approaches:

Option 1 (Recommended): Extract to a shared package:

// packages/ui/src/maintenance-banner.tsx
'use client';

export interface MaintenanceBannerProps {
    className?: string;
}

export function MaintenanceBanner({ className = 'bg-yellow-500 px-4 py-3 text-center text-sm font-medium text-black' }: MaintenanceBannerProps) {
    // ... shared logic
}

Then import in each platform with platform-specific styling if needed.

Option 2: If styling must differ significantly, extract just the hook logic:

// packages/hooks/src/use-motd.ts
export function useMotd() {
    const [motd, setMotd] = useState<Motd | null>(null);
    // ... fetch logic
    return motd;
}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import '@styles/globals.scss';
import { AuthContextProvider } from '@lib/context/auth-context';
import { ThemeContextProvider } from '@lib/context/theme-context';
import { AppHead } from '@components/common/app-head';
import { MaintenanceBanner } from '@components/common/maintenance-banner';
import type { ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';
Expand All @@ -24,6 +25,7 @@ export default function App({
return (
<>
<AppHead />
<MaintenanceBanner />
<AuthContextProvider>
<ThemeContextProvider>
{getLayout(<Component {...pageProps} />)}
Expand Down
2 changes: 2 additions & 0 deletions platforms/eVoting/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import "./globals.css";
import { AuthProvider } from "@/lib/auth-context";
import { MaintenanceBanner } from "@/components/MaintenanceBanner";

export const metadata: Metadata = {
title: "eVoting Platform",
Expand All @@ -18,6 +19,7 @@ export default function RootLayout({
return (
<html lang="en">
<body>
<MaintenanceBanner />
<AuthProvider>
{children}
</AuthProvider>
Expand Down
38 changes: 38 additions & 0 deletions platforms/eVoting/src/components/MaintenanceBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { useEffect, useState } from 'react';
import axios from 'axios';

interface Motd {
status: 'up' | 'maintenance';
message: string;
}

export function MaintenanceBanner() {
const [motd, setMotd] = useState<Motd | null>(null);

useEffect(() => {
const fetchMotd = async () => {
try {
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
} catch (error) {
console.error('Failed to fetch motd:', error);
}
};

fetchMotd();
}, []);

if (motd?.status !== 'maintenance') {
return null;
}

return (
<div className="bg-yellow-500 px-4 py-3 text-center text-sm font-medium text-black">
⚠️ {motd.message}
</div>
);
}

2 changes: 2 additions & 0 deletions platforms/group-charter-manager/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { AuthProvider } from "@/components/auth/auth-provider";
import DisclaimerModal from "@/components/disclaimer-modal";
import { MaintenanceBanner } from "@/components/MaintenanceBanner";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -23,6 +24,7 @@ export default function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
<MaintenanceBanner />
<AuthProvider>
{children}
<Toaster />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { useEffect, useState } from 'react';
import axios from 'axios';

interface Motd {
status: 'up' | 'maintenance';
message: string;
}

export function MaintenanceBanner() {
const [motd, setMotd] = useState<Motd | null>(null);

useEffect(() => {
const fetchMotd = async () => {
try {
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
} catch (error) {
console.error('Failed to fetch motd:', error);
}
};

fetchMotd();
}, []);

if (motd?.status !== 'maintenance') {
return null;
}

return (
<div className="bg-yellow-500 px-4 py-3 text-center text-sm font-medium text-black">
⚠️ {motd.message}
</div>
);
}

4 changes: 4 additions & 0 deletions platforms/pictique/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Registry URL for fetching motd and other registry services
# Note: In SvelteKit, public environment variables must be prefixed with PUBLIC_
PUBLIC_REGISTRY_URL=http://localhost:4321

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { onMount } from 'svelte';
import axios from 'axios';
import { PUBLIC_REGISTRY_URL } from '$env/static/public';
let motd = $state<{ status: 'up' | 'maintenance'; message: string } | null>(null);
onMount(async () => {
try {
const registryUrl = PUBLIC_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
</script>

{#if motd?.status === 'maintenance'}
<div class="bg-yellow-500 px-4 py-3 text-center text-sm font-medium text-black">
⚠️ {motd.message}
</div>
{/if}
18 changes: 11 additions & 7 deletions platforms/pictique/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { onMount } from 'svelte';
import '../app.css';
import { page } from '$app/state';
import MaintenanceBanner from '$lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte';
let { children } = $props();
Expand Down Expand Up @@ -46,11 +47,14 @@
<img src="/images/Logo.svg" alt="logo" />
</main>
{:else}
<main
class="h-[100dvh] overflow-hidden {page.url.pathname.includes('/profile')
? 'px-0'
: 'px-4'} md:px-0"
>
{@render children()}
</main>
<div class="flex h-[100dvh] flex-col overflow-hidden">
<MaintenanceBanner />
<main
class="flex-1 overflow-hidden {page.url.pathname.includes('/profile')
? 'px-0'
: 'px-4'} md:px-0"
>
{@render children()}
</main>
</div>
Comment on lines +50 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Review overflow handling to ensure content remains scrollable.

The wrapper div has overflow-hidden while the inner main also has overflow-hidden and flex-1. This combination may prevent the main content from scrolling when it exceeds the available viewport height (especially when the maintenance banner is displayed).

Consider:

  • Removing overflow-hidden from the wrapper div, or
  • Changing the inner main to use overflow-y-auto instead of overflow-hidden

Apply this diff to allow main content to scroll:

-	<div class="flex h-[100dvh] flex-col overflow-hidden">
+	<div class="flex h-[100dvh] flex-col">
 		<MaintenanceBanner />
 		<main
-			class="flex-1 overflow-hidden {page.url.pathname.includes('/profile')
+			class="flex-1 overflow-y-auto {page.url.pathname.includes('/profile')
 				? 'px-0'
 				: 'px-4'}  md:px-0"
 		>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="flex h-[100dvh] flex-col overflow-hidden">
<MaintenanceBanner />
<main
class="flex-1 overflow-hidden {page.url.pathname.includes('/profile')
? 'px-0'
: 'px-4'} md:px-0"
>
{@render children()}
</main>
</div>
<div class="flex h-[100dvh] flex-col">
<MaintenanceBanner />
<main
class="flex-1 overflow-y-auto {page.url.pathname.includes('/profile')
? 'px-0'
: 'px-4'} md:px-0"
>
{@render children()}
</main>
</div>
🤖 Prompt for AI Agents
In platforms/pictique/src/routes/+layout.svelte around lines 50 to 59, the outer
wrapper and inner main both use overflow-hidden which can prevent scrolling of
main content; remove overflow-hidden from the wrapper div (or at minimum change
the inner main's overflow to overflow-y-auto) so the main can scroll when its
content exceeds the viewport; update the class on the wrapper to drop
overflow-hidden and/or change main's class to use overflow-y-auto (and keep
flex-1) so the page remains scrollable when the maintenance banner or other
content reduces available height.

{/if}
4 changes: 4 additions & 0 deletions platforms/registry/motd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"status": "maintenance",
"message": "Registry service is down"
}
22 changes: 21 additions & 1 deletion platforms/registry/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import fastify from "fastify";
import { generateEntropy, generatePlatformToken, getJWK } from "./jwt";
import dotenv from "dotenv";
import path from "path";
import path from "node:path";
import { AppDataSource } from "./config/database";
import { VaultService } from "./services/VaultService";
import { UriResolutionService } from "./services/UriResolutionService";
import { KubernetesService } from "./services/KubernetesService";
import cors from "@fastify/cors";

import fs from "node:fs";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Synchronous I/O blocks the event loop and lacks error handling.

The implementation has several serious issues:

  1. Blocking I/O: fs.readFileSync blocks the event loop during startup and file reloads, degrading server responsiveness.
  2. No error handling: Missing try-catch will crash the server if the file is missing or contains invalid JSON.
  3. Path resolution risk: __dirname may not resolve correctly after TypeScript compilation, depending on build configuration.

Apply this diff to use async I/O with proper error handling:

-import fs from "node:fs";
+import fs from "node:fs/promises";

-function loadMotdJSON() {
-    const motdJSON = fs.readFileSync(path.resolve(__dirname, "../motd.json"), "utf8");
-    return JSON.parse(motdJSON) as {
+async function loadMotdJSON() {
+    try {
+        const motdPath = path.resolve(__dirname, "../motd.json");
+        const motdJSON = await fs.readFile(motdPath, "utf8");
+        return JSON.parse(motdJSON) as {
+            status: "up" | "maintenance"
+            message: string
+        };
+    } catch (error) {
+        server.log.error({ message: "Failed to load MOTD", error });
+        return {
+            status: "up" as const,
+            message: "Registry service is running"
+        };
+    }
+}
+
+let motd = {
-        status: "up" | "maintenance"
-        message: string
-    };
+    status: "up" as const,
+    message: "Registry service is running"
-}
+};

-let motd = loadMotdJSON();
+(async () => {
+    motd = await loadMotdJSON();
+})();

Note: You'll also need to update the file watcher (see next comment).

Also applies to: 13-19, 21-21


function loadMotdJSON() {
const motdJSON = fs.readFileSync(path.resolve(__dirname, "../motd.json"), "utf8");
return JSON.parse(motdJSON) as {
status: "up" | "maintenance"
message: string
};
}

let motd = loadMotdJSON();

fs.watchFile(path.resolve(__dirname, "../motd.json"), (_curr, _prev) => {
motd = loadMotdJSON();
});
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Use fs.watch instead of fs.watchFile and add cleanup.

fs.watchFile uses inefficient stat polling and creates a resource leak without cleanup.

Apply this diff to use event-based watching with proper cleanup:

-fs.watchFile(path.resolve(__dirname, "../motd.json"), (_curr, _prev) => {
-    motd = loadMotdJSON();
-});
+const motdWatcher = fs.watch(
+    path.resolve(__dirname, "../motd.json"),
+    async (eventType) => {
+        if (eventType === "change") {
+            motd = await loadMotdJSON();
+            server.log.info("MOTD reloaded");
+        }
+    }
+);
+
+// Cleanup on server close
+server.addHook("onClose", async () => {
+    motdWatcher.close();
+});

Note: This assumes you've converted loadMotdJSON() to async as suggested in the previous comment.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fs.watchFile(path.resolve(__dirname, "../motd.json"), (_curr, _prev) => {
motd = loadMotdJSON();
});
const motdWatcher = fs.watch(
path.resolve(__dirname, "../motd.json"),
async (eventType) => {
if (eventType === "change") {
motd = await loadMotdJSON();
server.log.info("MOTD reloaded");
}
}
);
// Cleanup on server close
server.addHook("onClose", async () => {
motdWatcher.close();
});


dotenv.config({ path: path.resolve(__dirname, "../../../.env") });

const server = fastify({ logger: true });
Expand Down Expand Up @@ -55,6 +71,10 @@ const checkSharedSecret = async (request: any, reply: any) => {
}
};

server.get("/motd", async (request, reply) => {
return motd;
});

// Create a new vault entry
server.post(
"/register",
Expand Down
Loading