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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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.tsexportinterfaceMotd{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
Copy Markdown
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(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`);
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
},[]);
useEffect(()=>{
constfetchMotd=async()=>{
try{
constregistryUrl=process.env.NEXT_PUBLIC_REGISTRY_URL||'http://localhost:4321';
constresponse=awaitaxios.get<Motd>(`${registryUrl}/motd`,{
timeout: 5000,// 5 second timeout
});
setMotd(response.data);
}catch(error){
console.error('Failed to fetch motd:',error);
}
};
voidfetchMotd();
// Optionally refetch every 5 minutes to catch status changes
constinterval=setInterval(()=>voidfetchMotd(),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
Copy Markdown
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';exportinterfaceMaintenanceBannerProps{className?: string;}exportfunctionMaintenanceBanner({ 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.tsexportfunctionuseMotd(){const[motd,setMotd]=useState<Motd|null>(null);// ... fetch logicreturnmotd;}


2 changes: 2 additions & 0 deletions platforms/blabsy/src/pages/_app.tsx
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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);
}
});
Comment on lines +8 to +16

Copy link
Copy Markdown
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 improve error handling for the MOTD fetch.

The axios request lacks a timeout configuration, which could cause the fetch to hang indefinitely if the registry is unresponsive. Additionally, the error handling only logs to console without providing any user feedback or fallback behavior.

Apply this diff to add timeout and improve reliability:

 onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
- const response = await axios.get(`${registryUrl}/motd`);+ const response = await axios.get(`${registryUrl}/motd`, {+ timeout: 5000,+ });
motd = response.data;
} catch (error) {
- console.error('Failed to fetch motd:', error);+ console.error('Failed to fetch MOTD:', error);+ // Silently fail - no banner shown if fetch fails
}
});
📝 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
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`);
motd = response.data;
} catch (error) {
console.error('Failed to fetch motd:', error);
}
});
onMount(async () => {
try {
const registryUrl = import.meta.env.VITE_REGISTRY_URL || 'http://localhost:4321';
const response = await axios.get(`${registryUrl}/motd`, {
timeout: 5000,
});
motd = response.data;
} catch (error) {
console.error('Failed to fetch MOTD:', error);
// Silently fail - no banner shown if fetch fails
}
});
🤖 Prompt for AI Agents
In
platforms/pictique/src/lib/fragments/MaintenanceBanner/MaintenanceBanner.svelte
around lines 7 to 15, the MOTD fetch has no timeout and only logs errors to
console; update the axios.get call to include a timeout (e.g. 2–5s) and check
the HTTP status, and in the catch branch set a safe fallback for motd (like a
default message or null) and a reactive error flag so the UI can show
user-visible feedback; also differentiate timeout/network errors vs non-2xx
responses in the error handling and ensure the error is logged with details for
debugging.

</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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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
<divclass="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"
>
{@renderchildren()}
</main>
</div>
<divclass="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"
>
{@renderchildren()}
</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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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();
});
constmotdWatcher=fs.watch(
path.resolve(__dirname,"../motd.json"),
async(eventType)=>{
if(eventType==="change"){
motd=awaitloadMotdJSON();
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 DownExpand 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