Update dependencies and version number, remove link tickets endpoint

This commit is contained in:
Darren Clarke 2025-10-07 11:24:00 +02:00
parent 6f0f97ab7b
commit 11563a794e
36 changed files with 2953 additions and 4655 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/bridge-frontend", "name": "@link-stack/bridge-frontend",
"version": "3.1.0", "version": "3.2.0b3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@ -13,26 +13,26 @@
"migrate:down:one": "tsx database/migrate.ts down:one" "migrate:down:one": "tsx database/migrate.ts down:one"
}, },
"dependencies": { "dependencies": {
"@auth/kysely-adapter": "^1.8.0", "@auth/kysely-adapter": "^1.10.0",
"@mui/icons-material": "^6", "@mui/icons-material": "^6",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/material-nextjs": "^6", "@mui/material-nextjs": "^6",
"@mui/x-license": "^7.28.0", "@mui/x-license": "^7",
"@link-stack/bridge-common": "*", "@link-stack/bridge-common": "*",
"@link-stack/bridge-ui": "*", "@link-stack/bridge-ui": "*",
"next": "15.2.3", "next": "15.5.4",
"next-auth": "^4.24.11", "next-auth": "^4.24.11",
"react": "19.0.0", "react": "19.2.0",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"sharp": "^0.33.5", "sharp": "^0.34.4",
"tsx": "^4.19.3", "tsx": "^4.20.6",
"@link-stack/ui": "*" "@link-stack/ui": "*"
}, },
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@types/node": "^22", "@types/node": "^24",
"@types/pg": "^8.11.11", "@types/pg": "^8.15.5",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/bridge-migrations", "name": "@link-stack/bridge-migrations",
"version": "3.1.0", "version": "3.2.0b3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"migrate:up:all": "tsx migrate.ts up:all", "migrate:up:all": "tsx migrate.ts up:all",
@ -10,14 +10,14 @@
}, },
"dependencies": { "dependencies": {
"@link-stack/logger": "*", "@link-stack/logger": "*",
"dotenv": "^16.4.7", "dotenv": "^17.2.3",
"kysely": "0.27.6", "kysely": "0.27.5",
"pg": "^8.14.1", "pg": "^8.16.3",
"tsx": "^4.19.3" "tsx": "^4.20.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22", "@types/node": "^24",
"@types/pg": "^8.11.11", "@types/pg": "^8.15.5",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"typescript": "^5" "typescript": "^5"

View file

@ -1,27 +1,27 @@
{ {
"name": "@link-stack/bridge-whatsapp", "name": "@link-stack/bridge-whatsapp",
"version": "3.1.0", "version": "3.2.0b3",
"main": "build/main/index.js", "main": "build/main/index.js",
"author": "Darren Clarke <darren@redaranj.com>", "author": "Darren Clarke <darren@redaranj.com>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@adiwajshing/keyed-db": "0.2.4", "@adiwajshing/keyed-db": "0.2.4",
"@hapi/hapi": "^21.4.0", "@hapi/hapi": "^21.4.3",
"@hapipal/schmervice": "^3.0.0", "@hapipal/schmervice": "^3.0.0",
"@hapipal/toys": "^4.0.0", "@hapipal/toys": "^4.0.0",
"@link-stack/logger": "*", "@link-stack/logger": "*",
"@whiskeysockets/baileys": "^6.7.16", "@whiskeysockets/baileys": "^6.7.20",
"hapi-pino": "^12.1.0", "hapi-pino": "^13.0.0",
"link-preview-js": "^3.0.14" "link-preview-js": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/jest-config": "*", "@link-stack/jest-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@types/node": "*", "@types/node": "*",
"dotenv-cli": "^8.0.0", "dotenv-cli": "^10.0.0",
"tsx": "^4.19.3", "tsx": "^4.20.6",
"typescript": "^5.8.2" "typescript": "^5.9.3"
}, },
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",

View file

@ -42,7 +42,7 @@ export const SendMessageRoute = withDefaults({
attachmentCount: attachments?.length || 0, attachmentCount: attachments?.length || 0,
}, },
"Sent a message at %s", "Sent a message at %s",
new Date(), new Date().toISOString(),
); );
return _h return _h
@ -69,7 +69,7 @@ export const ReceiveMessageRoute = withDefaults({
const date = new Date(); const date = new Date();
const twoDaysAgo = new Date(date.getTime()); const twoDaysAgo = new Date(date.getTime());
twoDaysAgo.setDate(date.getDate() - 2); twoDaysAgo.setDate(date.getDate() - 2);
request.logger.info({ id }, "Received messages at %s", new Date()); request.logger.info({ id }, "Received messages at %s", new Date().toISOString());
return whatsappService.receive(id, twoDaysAgo); return whatsappService.receive(id, twoDaysAgo);
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/bridge-worker", "name": "@link-stack/bridge-worker",
"version": "3.1.0", "version": "3.2.0b3",
"type": "module", "type": "module",
"main": "build/main/index.js", "main": "build/main/index.js",
"author": "Darren Clarke <darren@redaranj.com>", "author": "Darren Clarke <darren@redaranj.com>",
@ -17,14 +17,14 @@
"@link-stack/signal-api": "*", "@link-stack/signal-api": "*",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"graphile-worker": "^0.16.6", "graphile-worker": "^0.16.6",
"remeda": "^2.21.2", "remeda": "^2.32.0",
"twilio": "^5.5.1" "twilio": "^5.10.2"
}, },
"devDependencies": { "devDependencies": {
"@types/fluent-ffmpeg": "^2.1.27", "@types/fluent-ffmpeg": "^2.1.27",
"dotenv-cli": "^8.0.0", "dotenv-cli": "^10.0.0",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"typescript": "^5.8.2" "typescript": "^5.9.3"
} }
} }

View file

@ -1,6 +1,7 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" /> /// <reference types="next/navigation-types/compat/navigation" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/leafcutter", "name": "@link-stack/leafcutter",
"version": "3.1.0", "version": "3.2.0b3",
"scripts": { "scripts": {
"dev": "next dev -p 3001", "dev": "next dev -p 3001",
"login": "aws sso login --sso-session cdr", "login": "aws sso login --sso-session cdr",
@ -16,35 +16,35 @@
"@emotion/cache": "^11.14.0", "@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.1",
"@link-stack/leafcutter-ui": "*", "@link-stack/leafcutter-ui": "*",
"@link-stack/logger": "*", "@link-stack/logger": "*",
"@link-stack/opensearch-common": "*", "@link-stack/opensearch-common": "*",
"@mui/icons-material": "^6", "@mui/icons-material": "^6",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/material-nextjs": "^6", "@mui/material-nextjs": "^6",
"@mui/x-date-pickers-pro": "^7.28.0", "@mui/x-date-pickers-pro": "^7",
"@opensearch-project/opensearch": "^3.4.0", "@opensearch-project/opensearch": "^3.5.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"http-proxy-middleware": "^3.0.3", "http-proxy-middleware": "^3.0.5",
"material-ui-popup-state": "^5.3.3", "material-ui-popup-state": "^5.3.6",
"next": "15.2.3", "next": "15.5.4",
"next-auth": "^4.24.11", "next-auth": "^4.24.11",
"react": "19.0.0", "react": "19.2.0",
"react-cookie": "^8.0.1", "react-cookie": "^8.0.1",
"react-cookie-consent": "^9.0.0", "react-cookie-consent": "^9.0.0",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"react-iframe": "^1.8.5", "react-iframe": "^1.8.5",
"react-polyglot": "^0.7.2", "react-polyglot": "^0.7.2",
"sharp": "^0.33.5", "sharp": "^0.34.4",
"uuid": "^11.1.0" "uuid": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/uuid": "^10.0.0", "@types/uuid": "^11.0.0",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"typescript": "5.8.2" "typescript": "5.9.3"
} }
} }

View file

@ -1,10 +0,0 @@
import { Metadata } from "next";
import { ZammadWrapper } from "../../(main)/_components/ZammadWrapper";
export const metadata: Metadata = {
title: "Knowledge Base",
};
export default function Page() {
return <ZammadWrapper path="/#knowledge_base/1/locale/en-us" />;
}

View file

@ -1,100 +0,0 @@
"use client";
import { FC, useState } from "react";
import {
Grid,
Button,
Dialog,
DialogActions,
DialogContent,
TextField,
} from "@mui/material";
import { createTicketArticleAction } from "app/_actions/tickets";
interface ArticleCreateDialogProps {
ticketID: string;
open: boolean;
closeDialog: () => void;
kind: string;
recipient?: string;
}
export const ArticleCreateDialog: FC<ArticleCreateDialogProps> = ({
ticketID,
open,
closeDialog,
kind,
recipient,
}) => {
const [body, setBody] = useState("");
const backgroundColor = kind === "note" ? "#FFB620" : "#1982FC";
const color = kind === "note" ? "black" : "white";
const article = {
body,
type: kind,
internal: kind === "note",
};
if (kind === "email") {
article["to"] = recipient;
}
const createArticle = async () => {
await createTicketArticleAction(ticketID, article);
closeDialog();
setBody("");
};
return (
<Dialog open={open} maxWidth="sm" fullWidth>
<DialogContent>
<TextField
label={kind === "note" ? "Write internal note" : "Write reply"}
multiline
rows={10}
fullWidth
value={body}
onChange={(e: any) => setBody(e.target.value)}
/>
</DialogContent>
<DialogActions sx={{ px: 3, pt: 0, pb: 3 }}>
<Grid container justifyContent="space-between">
<Grid item>
<Button
sx={{
backgroundColor: "white",
color: "#666",
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
}}
onClick={() => {
setBody("");
closeDialog();
}}
>
Cancel
</Button>
</Grid>
<Grid item>
<Button
sx={{
backgroundColor,
color,
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
px: 3,
}}
onClick={createArticle}
>
{kind === "note" ? "Save Note" : "Send Reply"}
</Button>
</Grid>
</Grid>
</DialogActions>
</Dialog>
);
};

View file

@ -1,177 +0,0 @@
"use client";
import { FC, useState, useEffect } from "react";
import { getTicketAction, getTicketArticlesAction } from "app/_actions/tickets";
import { Grid, Box, Typography } from "@mui/material";
import { Button, fonts, colors } from "@link-stack/ui";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {
MainContainer,
ChatContainer,
MessageList,
Message,
ConversationHeader,
} from "@chatscope/chat-ui-kit-react";
import { ArticleCreateDialog } from "./ArticleCreateDialog";
interface TicketDetailProps {
id: string;
}
export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
const [ticket, setTicket] = useState<any>(null);
const [ticketArticles, setTicketArticles] = useState<any>(null);
const { poppins, roboto } = fonts;
const { veryLightGray, lightGray } = colors;
const [dialogOpen, setDialogOpen] = useState(false);
const [articleKind, setArticleKind] = useState("note");
useEffect(() => {
const fetchTicket = async () => {
const result = await getTicketAction(id);
setTicket(result);
};
fetchTicket();
const interval = setInterval(fetchTicket, 20000);
return () => clearInterval(interval);
}, [id]);
useEffect(() => {
const fetchTicketArticles = async () => {
const result = await getTicketArticlesAction(id);
setTicketArticles(result);
};
fetchTicketArticles();
const interval = setInterval(fetchTicketArticles, 5000);
return () => clearInterval(interval);
}, [id]);
const closeDialog = () => setDialogOpen(false);
const firstArticle = ticketArticles?.edges[0]?.node;
const firstArticleKind = firstArticle?.type?.name ?? "phone";
const firstEmailSender = firstArticle?.from?.parsed?.[0]?.emailAddress ?? "";
const recipient = firstEmailSender;
const shouldRender = !!ticket && !!ticketArticles;
return (
<Box sx={{ height: "100%", width: "100%", background: veryLightGray }}>
{shouldRender && (
<>
<MainContainer>
<ChatContainer>
<ConversationHeader>
<ConversationHeader.Content>
<Box
sx={{
width: "100%",
textAlign: "center",
fontWeight: "bold",
}}
>
<Typography
variant="h5"
sx={{
fontFamily: poppins.style.fontFamily,
fontWeight: 700,
}}
>
{ticket.title}
</Typography>
<Typography
variant="h6"
sx={{
fontFamily: roboto.style.fontFamily,
fontWeight: 400,
}}
>{`Ticket #${ticket.number} (created ${new Date(
ticket.createdAt,
).toLocaleDateString()})`}</Typography>
</Box>
</ConversationHeader.Content>
</ConversationHeader>
<MessageList style={{ marginBottom: 80 }}>
{ticketArticles.edges.map(({ node: article }: any) => (
<Message
key={article.id}
className={
article.internal
? "internal-note"
: article?.sender?.name === "Agent"
? "outgoing-message"
: "incoming-message"
}
model={{
message: article.bodyWithUrls,
type:
article.contentType === "text/html" ? "html" : "text",
sentTime: article.updated_at,
sender: article.from,
direction:
article.sender === "Agent" ? "outgoing" : "incoming",
position: "single",
}}
/>
))}
</MessageList>
</ChatContainer>
<Box
sx={{
height: 80,
background: veryLightGray,
borderTop: `1px solid ${lightGray}`,
position: "absolute",
bottom: 0,
width: "100%",
zIndex: 1000,
}}
>
<Grid
container
spacing={6}
justifyContent="center"
alignItems="center"
alignContent="center"
sx={{ height: "100%", pt: 6 }}
>
<Grid item>
<Button
text="Write note to agent"
color="#FFB620"
onClick={() => {
setArticleKind("note");
setDialogOpen(true);
}}
/>
</Grid>
<Grid item>
<Button
text="Reply to ticket"
kind="primary"
onClick={() => {
setArticleKind(firstArticleKind);
setDialogOpen(true);
}}
/>
</Grid>
</Grid>
</Box>
</MainContainer>
<ArticleCreateDialog
ticketID={ticket.internalId}
open={dialogOpen}
closeDialog={closeDialog}
kind={articleKind}
recipient={recipient}
/>
</>
)}
</Box>
);
};

View file

@ -1,13 +0,0 @@
import { TicketDetail } from "./_components/TicketDetail";
type PageProps = {
params: Promise<{
id: string;
}>;
};
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <TicketDetail id={id} />;
}

View file

@ -1,201 +0,0 @@
"use client";
import { FC, useEffect, useState } from "react";
import { Grid, Box } from "@mui/material";
import { Select, Button } from "@link-stack/ui";
import { MuiChipsInput } from "mui-chips-input";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import {
updateTicketAction,
getTicketAction,
getTicketStatesAction,
getTicketPrioritiesAction,
} from "app/_actions/tickets";
import { getAgentsAction } from "app/_actions/users";
import { getGroupsAction } from "app/_actions/groups";
interface TicketEditProps {
id: string;
}
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
const [ticket, setTicket] = useState<any>();
const [hasChanges, setHasChanges] = useState(false);
const [formState, setFormState] = useState({
values: {
group: null,
owner: null,
priority: null,
pendingTime: null,
state: null,
tags: [],
},
});
const [ticketStates, setTicketStates] = useState<any>();
const [ticketPriorities, setTicketPriorities] = useState<any>();
const [groups, setGroups] = useState<any>();
const [agents, setAgents] = useState<any>();
const [pendingVisible, setPendingVisible] = useState(false);
useEffect(() => {
const fetchAgents = async () => {
const groupID = formState?.values?.group?.split("/")?.pop();
const result = await getAgentsAction(groupID);
setAgents(result);
};
const fetchGroups = async () => {
const result = await getGroupsAction();
setGroups(result);
};
const fetchTicketStates = async () => {
const result = await getTicketStatesAction();
setTicketStates(result);
};
const fetchTicketPriorities = async () => {
const result = await getTicketPrioritiesAction();
setTicketPriorities(result);
};
fetchTicketStates();
fetchTicketPriorities();
fetchAgents();
fetchGroups();
}, [formState.values.group]);
useEffect(() => {
const fetchTicket = async () => {
const result = await getTicketAction(id);
setTicket(result);
setFormState({
values: {
...formState.values,
group: result?.group?.id,
owner: result?.owner?.id,
priority: result?.priority?.id,
state: result?.state?.id,
tags: result?.tags,
},
});
};
fetchTicket();
}, []);
const updateFormState = (name: string, value: any) => {
setFormState({
values: {
...formState.values,
[name]: value,
},
});
const stateName = ticketStates?.find(
(state: any) => state.id === formState.values.state,
)?.name;
setPendingVisible(stateName?.includes("pending") ?? false);
setHasChanges(true);
};
const updateTicket = async () => {
await updateTicketAction(id, formState.values);
setHasChanges(false);
};
const shouldRender = !!ticket;
return (
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
{shouldRender && (
<Grid container direction="column" spacing={3}>
<Grid item>
<Box sx={{ m: 1 }}>Group</Box>
<Select
name="group"
label="Group"
formState={formState}
updateFormState={updateFormState}
getOptions={() => groups}
/>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
<Select
name="owner"
label="Owner"
formState={formState}
updateFormState={updateFormState}
getOptions={() => agents}
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ m: 1, mt: 0 }}>State</Box>
<Select
name="state"
label="State"
formState={formState}
updateFormState={updateFormState}
getOptions={() => ticketStates}
/>
</Grid>
<Grid
item
xs={12}
sx={{ display: pendingVisible ? "inherit" : "none" }}
>
<DatePicker
label="Pending Date"
value={new Date(formState.values.pendingTime)}
onChange={(newValue: any) => {
updateFormState("pendingDate", newValue.toISOString());
}}
slotProps={{ textField: { size: "small" } }}
sx={{
width: "100%",
backgroundColor: "white",
}}
/>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
<Select
name="priority"
label="Priority"
formState={formState}
updateFormState={updateFormState}
getOptions={() => ticketPriorities}
/>
</Grid>
<Grid item>
<Box sx={{ mb: 1 }}>Tags</Box>
<MuiChipsInput
sx={{ backgroundColor: "white", width: "100%" }}
value={formState.values.tags}
hideClearAll
onChange={(tags: any) => {
updateFormState("tags", tags);
}}
onDeleteChip={(tag: any) => {
const tags = formState.values.tags.filter(
(t: any) => t !== tag,
);
updateFormState("tags", tags);
}}
/>
</Grid>
<Grid item container direction="row-reverse">
<Grid item>
<Button
text="Save"
kind="primary"
onClick={updateTicket}
disabled={!hasChanges}
/>
</Grid>
</Grid>
</Grid>
)}
</Box>
);
};

View file

@ -1,13 +0,0 @@
import { TicketEdit } from "./_components/TicketEdit";
type PageProps = {
params: Promise<{
id: string;
}>;
};
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <TicketEdit id={id} />;
}

View file

@ -1,11 +0,0 @@
"use client";
import { DisplayError } from "app/_components/DisplayError";
type PageProps = {
error: Error;
};
export default function Page({ error }: PageProps) {
return <DisplayError error={error} />;
}

View file

@ -1,19 +0,0 @@
import { Grid } from "@mui/material";
type LayoutProps = {
detail: any;
edit: any;
};
export default async function Layout({ detail, edit }: LayoutProps) {
return (
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
<Grid item sx={{ height: "100vh" }} xs={9}>
{detail}
</Grid>
<Grid item xs={3} sx={{ height: "100vh" }}>
{edit}
</Grid>
</Grid>
);
}

View file

@ -1,15 +0,0 @@
"use client";
import Link from "next/link";
export default function Page() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<p>
View <Link href="/blog">all posts</Link>
</p>
</div>
);
}

View file

@ -58,10 +58,11 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
opensearchBaseURL, opensearchBaseURL,
headers, headers,
); );
} else { }
const isDev = process.env.NODE_ENV === "development";
const nonce = Buffer.from(crypto.randomUUID()).toString("base64"); const isDev = process.env.NODE_ENV === "development";
const cspHeader = ` const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const cspHeader = `
default-src 'self'; default-src 'self';
frame-src 'self' https://digiresilience.org; frame-src 'self' https://digiresilience.org;
connect-src 'self'; connect-src 'self';
@ -75,30 +76,29 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
frame-ancestors 'self'; frame-ancestors 'self';
upgrade-insecure-requests; upgrade-insecure-requests;
`; `;
const contentSecurityPolicyHeaderValue = cspHeader const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, " ") .replace(/\s{2,}/g, " ")
.trim(); .trim();
const requestHeaders = new Headers(request.headers); const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce); requestHeaders.set("x-nonce", nonce);
requestHeaders.set( requestHeaders.set(
"Content-Security-Policy", "Content-Security-Policy",
contentSecurityPolicyHeaderValue, contentSecurityPolicyHeaderValue,
); );
const response = NextResponse.next({ const response = NextResponse.next({
request: { request: {
headers: requestHeaders, headers: requestHeaders,
}, },
}); });
response.headers.set( response.headers.set(
"Content-Security-Policy", "Content-Security-Policy",
contentSecurityPolicyHeaderValue, contentSecurityPolicyHeaderValue,
); );
return response; return response;
};
} }
export default withAuth(checkRewrites, { export default withAuth(checkRewrites, {

View file

@ -1,5 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/link", "name": "@link-stack/link",
"version": "3.1.0", "version": "3.2.0b3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev -H 0.0.0.0", "dev": "next dev -H 0.0.0.0",
@ -10,12 +10,12 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@chatscope/chat-ui-kit-react": "^2.0.3", "@chatscope/chat-ui-kit-react": "^2.1.1",
"@chatscope/chat-ui-kit-styles": "^1.4.0", "@chatscope/chat-ui-kit-styles": "^1.4.0",
"@emotion/cache": "^11.14.0", "@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.1",
"@link-stack/bridge-common": "*", "@link-stack/bridge-common": "*",
"@link-stack/bridge-ui": "*", "@link-stack/bridge-ui": "*",
"@link-stack/leafcutter-ui": "*", "@link-stack/leafcutter-ui": "*",
@ -25,27 +25,27 @@
"@mui/icons-material": "^6", "@mui/icons-material": "^6",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/material-nextjs": "^6", "@mui/material-nextjs": "^6",
"@mui/x-data-grid-pro": "^7.28.1", "@mui/x-data-grid-pro": "^7",
"@mui/x-date-pickers": "^7.28.0", "@mui/x-date-pickers": "^7",
"@mui/x-date-pickers-pro": "^7.28.0", "@mui/x-date-pickers-pro": "^7",
"@mui/x-license": "^7.28.0", "@mui/x-license": "^7",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"graphql-request": "^7.1.2", "graphql-request": "^7.2.0",
"ioredis": "^5.6.0", "ioredis": "^5.8.1",
"mui-chips-input": "^6.0.0", "mui-chips-input": "^6.0.0",
"next": "15.2.3", "next": "15.5.4",
"next-auth": "^4.24.11", "next-auth": "^4.24.11",
"react": "19.0.0", "react": "19.2.0",
"react-cookie": "^8.0.1", "react-cookie": "^8.0.1",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"react-iframe": "^1.8.5", "react-iframe": "^1.8.5",
"react-polyglot": "^0.7.2", "react-polyglot": "^0.7.2",
"sharp": "^0.33.5" "sharp": "^0.34.4"
}, },
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/uuid": "^10.0.0" "@types/uuid": "^11.0.0"
} }
} }

View file

@ -1,4 +1,4 @@
ARG ZAMMAD_VERSION=6.5.0 ARG ZAMMAD_VERSION=6.5.2
FROM node:22-slim AS node FROM node:22-slim AS node
@ -24,19 +24,19 @@ RUN contrib/docker/setup.sh builder
ARG EMBEDDED=false ARG EMBEDDED=false
ARG LINK_HOST=http://link:3000 ARG LINK_HOST=http://link:3000
RUN if [ "$EMBEDDED" = "true" ] ; then sed -i "/location \/ {/i\ RUN if [ "$EMBEDDED" = "true" ] ; then sed -i "/location \/ {/i\
location /link {\n\ location /link {\n\
proxy_pass ${LINK_HOST};\n\ proxy_pass ${LINK_HOST};\n\
proxy_set_header Host \$host;\n\ proxy_set_header Host \$host;\n\
proxy_set_header X-Real-IP \$remote_addr;\n\ proxy_set_header X-Real-IP \$remote_addr;\n\
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n\ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n\
proxy_set_header X-Forwarded-Proto https;\n\ proxy_set_header X-Forwarded-Proto https;\n\
}\n\ }\n\
" ${ZAMMAD_DIR}/contrib/nginx/zammad.conf; fi " ${ZAMMAD_DIR}/contrib/nginx/zammad.conf; fi
RUN sed -i '/^[[:space:]]*# es config/a\ RUN sed -i '/^[[:space:]]*# es config/a\
echo "about to reinstall..."\n\ echo "about to reinstall..."\n\
bundle exec rails runner /opt/zammad/contrib/link/setup.rb\n\ bundle exec rails runner /opt/zammad/contrib/link/setup.rb\n\
bundle exec rake zammad:package:migrate\n\ bundle exec rake zammad:package:migrate\n\
' /docker-entrypoint.sh ' /docker-entrypoint.sh
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS runner FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS runner
USER zammad USER zammad

6675
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,13 @@
{ {
"name": "@link-stack", "name": "@link-stack",
"version": "3.1.0", "version": "3.2.0b3",
"description": "Link from the Center for Digital Resilience", "description": "Link from the Center for Digital Resilience",
"scripts": { "scripts": {
"dev": "dotenv -- turbo dev", "dev": "dotenv -- turbo dev",
"build": "dotenv -- turbo build", "build": "dotenv -- turbo build",
"migrate": "dotenv -- npm run migrate --workspace=database", "migrate": "dotenv -- npm run migrate --workspace=database",
"lint": "dotenv turbo lint", "lint": "dotenv turbo lint",
"update-version": "find . -name 'package.json' -exec sed -i -E 's/\"version\": \"[^\"]+\"/\"version\": \"3.1.0\"/' {} +", "update-version": "find . -name 'package.json' -exec sed -i -E 's/\"version\": \"[^\"]+\"/\"version\": \"3.2.0b3\"/' {} +",
"upgrade:setup": "npm i -g npm-check-updates", "upgrade:setup": "npm i -g npm-check-updates",
"upgrade:check": "ncu && ncu -ws", "upgrade:check": "ncu && ncu -ws",
"upgrade": "ncu -u && ncu -ws -u && npm i", "upgrade": "ncu -u && ncu -ws -u && npm i",
@ -48,25 +48,25 @@
"type": "git", "type": "git",
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git" "url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
}, },
"packageManager": "npm@11.2.0", "packageManager": "npm@11.6.1",
"author": "Darren Clarke", "author": "Darren Clarke",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/react-dom": "19.0.4", "@types/react-dom": "19.2.1",
"dotenv-cli": "latest", "dotenv-cli": "latest",
"eslint": "^9", "eslint": "^9",
"react": "19.0.0", "react": "19.2.0",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"turbo": "^2.4.4", "turbo": "^2.5.8",
"typescript": "latest" "typescript": "latest"
}, },
"overrides": { "overrides": {
"react": "19.0.0", "react": "19.2.0",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/react-dom": "19.0.4", "@types/react-dom": "19.2.1",
"@mui/material": "^6.5.0" "@mui/material": "^6.5.0"
}, },
"engines": { "engines": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/bridge-common", "name": "@link-stack/bridge-common",
"version": "3.1.0", "version": "3.2.0b3",
"main": "build/main/index.js", "main": "build/main/index.js",
"type": "module", "type": "module",
"author": "Darren Clarke <darren@redaranj.com>", "author": "Darren Clarke <darren@redaranj.com>",
@ -9,14 +9,14 @@
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@auth/kysely-adapter": "^1.8.0", "@auth/kysely-adapter": "^1.10.0",
"graphile-worker": "^0.16.6", "graphile-worker": "^0.16.6",
"kysely": "0.27.6", "kysely": "0.27.5",
"pg": "^8.14.1" "pg": "^8.16.3"
}, },
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"typescript": "^5.8.2" "typescript": "^5.9.3"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/bridge-ui", "name": "@link-stack/bridge-ui",
"version": "3.1.0", "version": "3.2.0b3",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
}, },
@ -9,17 +9,17 @@
"@link-stack/signal-api": "*", "@link-stack/signal-api": "*",
"@link-stack/ui": "*", "@link-stack/ui": "*",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/x-data-grid-pro": "^7.28.1", "@mui/x-data-grid-pro": "^7",
"kysely": "0.27.6", "kysely": "0.27.5",
"next": "15.2.3", "next": "15.5.4",
"react": "19.0.0", "react": "19.2.0",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"react-qr-code": "^2.0.15" "react-qr-code": "^2.0.18"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.2.1",
"typescript": "5.8.2" "typescript": "5.9.3"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/eslint-config", "name": "@link-stack/eslint-config",
"version": "3.1.0", "version": "3.2.0b3",
"description": "amigo's eslint config", "description": "amigo's eslint config",
"author": "Abel Luck <abel@guardianproject.info>", "author": "Abel Luck <abel@guardianproject.info>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@ -9,24 +9,24 @@
"fmt": "prettier \"profile/**/*.js\" --write" "fmt": "prettier \"profile/**/*.js\" --write"
}, },
"dependencies": { "dependencies": {
"@rushstack/eslint-patch": "^1.11.0", "@rushstack/eslint-patch": "^1.13.0",
"@typescript-eslint/eslint-plugin": "^8.27.0", "@typescript-eslint/eslint-plugin": "^8.46.0",
"@typescript-eslint/parser": "^8.27.0", "@typescript-eslint/parser": "^8.46.0",
"eslint-config-prettier": "^10.1.1", "eslint-config-prettier": "^10.1.8",
"eslint-config-xo-space": "^0.35.0", "eslint-config-xo-space": "^0.35.0",
"eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^28.11.0", "eslint-plugin-jest": "^29.0.1",
"eslint-plugin-promise": "^7.2.1", "eslint-plugin-promise": "^7.2.1",
"eslint-plugin-unicorn": "58.0.0", "eslint-plugin-unicorn": "61.0.2",
"@babel/eslint-parser": "7.26.10" "@babel/eslint-parser": "7.28.4"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^4.9.5" "typescript": "^4.9.5"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^9", "eslint": "^9",
"jest": "^29.7.0", "jest": "^30.2.0",
"typescript": "^5.8.2" "typescript": "^5.9.3"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/jest-config", "name": "@link-stack/jest-config",
"version": "3.1.0", "version": "3.2.0b3",
"description": "", "description": "",
"author": "Abel Luck <abel@guardianproject.info>", "author": "Abel Luck <abel@guardianproject.info>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@ -9,8 +9,8 @@
"node": ">=14" "node": ">=14"
}, },
"dependencies": { "dependencies": {
"@types/jest": "^29.5.14", "@types/jest": "^30.0.0",
"jest": "^29.7.0", "jest": "^30.2.0",
"jest-junit": "^16.0.0" "jest-junit": "^16.0.0"
}, },
"peerDependencies": {} "peerDependencies": {}

View file

@ -1,23 +1,23 @@
{ {
"name": "@link-stack/leafcutter-ui", "name": "@link-stack/leafcutter-ui",
"version": "3.1.0", "version": "3.2.0b3",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.1",
"@link-stack/opensearch-common": "*", "@link-stack/opensearch-common": "*",
"@mui/icons-material": "^6", "@mui/icons-material": "^6",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/x-data-grid-pro": "^7.28.1", "@mui/x-data-grid-pro": "^7",
"@mui/x-date-pickers-pro": "^7.28.0", "@mui/x-date-pickers-pro": "^7",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"next": "15.2.3", "next": "15.5.4",
"next-auth": "^4.24.11", "next-auth": "^4.24.11",
"react": "19.0.0", "react": "19.2.0",
"react-cookie": "^8.0.1", "react-cookie": "^8.0.1",
"react-dom": "19.0.0", "react-dom": "19.2.0",
"react-iframe": "^1.8.5", "react-iframe": "^1.8.5",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-polyglot": "^0.7.2" "react-polyglot": "^0.7.2"
@ -25,8 +25,8 @@
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"typescript": "5.8.2" "typescript": "5.9.3"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/logger", "name": "@link-stack/logger",
"version": "1.0.0", "version": "3.2.0b3",
"description": "Shared logging utility for Link Stack monorepo", "description": "Shared logging utility for Link Stack monorepo",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.mjs", "module": "./dist/index.mjs",
@ -19,16 +19,16 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"pino": "^9.5.0", "pino": "^10.0.0",
"pino-pretty": "^13.0.0" "pino-pretty": "^13.1.1"
}, },
"devDependencies": { "devDependencies": {
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@types/node": "^22.10.5", "@types/node": "^24.7.0",
"eslint": "^9.17.0", "eslint": "^9.37.0",
"tsup": "^8.3.5", "tsup": "^8.5.0",
"typescript": "^5.7.3" "typescript": "^5.9.3"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View file

@ -1,19 +1,19 @@
{ {
"name": "@link-stack/opensearch-common", "name": "@link-stack/opensearch-common",
"version": "3.1.0", "version": "3.2.0b3",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@link-stack/logger": "*", "@link-stack/logger": "*",
"@opensearch-project/opensearch": "^3.4.0", "@opensearch-project/opensearch": "^3.5.1",
"uuid": "^11.1.0" "uuid": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/uuid": "^10.0.0", "@types/uuid": "^11.0.0",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"typescript": "5.8.2" "typescript": "5.9.3"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/signal-api", "name": "@link-stack/signal-api",
"version": "3.1.0", "version": "3.2.0b3",
"type": "module", "type": "module",
"main": "build/index.js", "main": "build/index.js",
"exports": { "exports": {
@ -12,10 +12,10 @@
"update-api": "openapi-generator-cli generate -i 'https://bbernhard.github.io/signal-cli-rest-api/src/docs/swagger.json' -g typescript-fetch -o . --skip-validate-spec" "update-api": "openapi-generator-cli generate -i 'https://bbernhard.github.io/signal-cli-rest-api/src/docs/swagger.json' -g typescript-fetch -o . --skip-validate-spec"
}, },
"devDependencies": { "devDependencies": {
"@openapitools/openapi-generator-cli": "^2.18.4", "@openapitools/openapi-generator-cli": "^2.24.0",
"@link-stack/typescript-config": "*", "@link-stack/typescript-config": "*",
"@link-stack/eslint-config": "*", "@link-stack/eslint-config": "*",
"@types/node": "^22", "@types/node": "^24",
"typescript": "^5" "typescript": "^5"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/typescript-config", "name": "@link-stack/typescript-config",
"version": "3.1.0", "version": "3.2.0b3",
"description": "Shared TypeScript config", "description": "Shared TypeScript config",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"author": "Abel Luck <abel@guardianproject.info>", "author": "Abel Luck <abel@guardianproject.info>",

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/ui", "name": "@link-stack/ui",
"version": "3.1.0", "version": "3.2.0b3",
"description": "", "description": "",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json" "build": "tsc -p tsconfig.json"
@ -9,16 +9,16 @@
"dependencies": { "dependencies": {
"@mui/icons-material": "^6", "@mui/icons-material": "^6",
"@mui/material": "^6", "@mui/material": "^6",
"@mui/x-data-grid-pro": "^7.28.1", "@mui/x-data-grid-pro": "^7",
"@mui/x-license": "^7.28.0", "@mui/x-license": "^7",
"next": "15.2.3", "next": "15.5.4",
"react": "19.0.0", "react": "19.2.0",
"react-dom": "19.0.0" "react-dom": "19.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"@types/react": "19.0.12", "@types/react": "19.2.2",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.2.1",
"typescript": "^5.8.2" "typescript": "^5.9.3"
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "@link-stack/zammad-addon-bridge", "name": "@link-stack/zammad-addon-bridge",
"displayName": "Bridge", "displayName": "Bridge",
"version": "3.1.0", "version": "3.2.0b3",
"description": "An addon that adds CDR Bridge channels to Zammad.", "description": "An addon that adds CDR Bridge channels to Zammad.",
"scripts": { "scripts": {
"build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'", "build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'",

View file

@ -1,6 +1,6 @@
{ {
"name": "@link-stack/zammad-addon-common", "name": "@link-stack/zammad-addon-common",
"version": "3.1.0", "version": "3.2.0b3",
"description": "", "description": "",
"bin": { "bin": {
"zpm-build": "./dist/build.js", "zpm-build": "./dist/build.js",
@ -10,13 +10,13 @@
"build": "tsc" "build": "tsc"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.12", "@types/node": "^24.7.0",
"typescript": "^5" "typescript": "^5"
}, },
"author": "", "author": "",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@link-stack/logger": "*", "@link-stack/logger": "*",
"glob": "^11.0.1" "glob": "^11.0.3"
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "@link-stack/zammad-addon-hardening", "name": "@link-stack/zammad-addon-hardening",
"displayName": "Hardening", "displayName": "Hardening",
"version": "3.1.0", "version": "3.2.0b3",
"description": "A Zammad addon that hardens a Zammad instance according to CDR's needs.", "description": "A Zammad addon that hardens a Zammad instance according to CDR's needs.",
"scripts": { "scripts": {
"build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'", "build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'",

View file

@ -1,7 +1,7 @@
{ {
"name": "@link-stack/zammad-addon-leafcutter", "name": "@link-stack/zammad-addon-leafcutter",
"displayName": "Leafcutter", "displayName": "Leafcutter",
"version": "3.1.0", "version": "3.2.0b3",
"description": "Adds a common set of tags for Leafcutter uses.", "description": "Adds a common set of tags for Leafcutter uses.",
"scripts": { "scripts": {
"build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'", "build": "node '../../node_modules/@link-stack/zammad-addon-common/dist/build.js'",