Compare commits
4 Commits
fix/checkl
...
feat/log-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c28fbf2668 | ||
|
|
67cf523ee8 | ||
|
|
0b45ceff7f | ||
|
|
76b2c58461 |
@@ -11,10 +11,10 @@ https://kan.bn/api/v1
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require authentication using your API key. You can create one in the [settings page](https://kan.bn/settings) of your account. Include this key in the `x-api-key` header of each request.
|
||||
Most endpoints require authentication using your API key. You can create one in the [settings page](https://kan.bn/settings) of your account. Include this key as a Bearer token in the `Authorization` header of each request.
|
||||
|
||||
```
|
||||
'x-api-key': kan_123456789
|
||||
'Authorization': 'Bearer kan_123456789'
|
||||
```
|
||||
|
||||
## Response codes
|
||||
|
||||
130
apps/web/src/components/PlainTextEditor.tsx
Normal file
130
apps/web/src/components/PlainTextEditor.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface PlainTextEditorProps {
|
||||
content: string;
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: (value: string) => void;
|
||||
onEnter?: (value: string) => void;
|
||||
onEscape?: () => void;
|
||||
readOnly?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function PlainTextEditor({
|
||||
content,
|
||||
onChange,
|
||||
onBlur,
|
||||
onEnter,
|
||||
onEscape,
|
||||
readOnly = false,
|
||||
placeholder,
|
||||
className,
|
||||
}: PlainTextEditorProps) {
|
||||
const onEnterRef = useRef(onEnter);
|
||||
const onEscapeRef = useRef(onEscape);
|
||||
const onBlurRef = useRef(onBlur);
|
||||
const onChangeRef = useRef(onChange);
|
||||
const contentRef = useRef(content);
|
||||
|
||||
useEffect(() => {
|
||||
onEnterRef.current = onEnter;
|
||||
}, [onEnter]);
|
||||
useEffect(() => {
|
||||
onEscapeRef.current = onEscape;
|
||||
}, [onEscape]);
|
||||
useEffect(() => {
|
||||
onBlurRef.current = onBlur;
|
||||
}, [onBlur]);
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
useEffect(() => {
|
||||
contentRef.current = content;
|
||||
}, [content]);
|
||||
|
||||
const editor = useEditor(
|
||||
{
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
bold: false,
|
||||
italic: false,
|
||||
strike: false,
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
blockquote: false,
|
||||
heading: false,
|
||||
bulletList: false,
|
||||
orderedList: false,
|
||||
listItem: false,
|
||||
horizontalRule: false,
|
||||
hardBreak: false,
|
||||
}),
|
||||
Placeholder.configure({ placeholder }),
|
||||
],
|
||||
content,
|
||||
editable: !readOnly,
|
||||
onUpdate: ({ editor }) => onChangeRef.current?.(editor.getText()),
|
||||
onBlur: ({ editor }) => onBlurRef.current?.(editor.getText()),
|
||||
editorProps: {
|
||||
handleKeyDown: (view, event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
onEnterRef.current?.(view.state.doc.textContent.trim());
|
||||
return true;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
// Reset to original content before calling the callback
|
||||
editor?.commands.setContent(contentRef.current, false);
|
||||
editor?.commands.blur();
|
||||
onEscapeRef.current?.();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
attributes: {
|
||||
class: "outline-none focus:outline-none focus-visible:ring-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
if (content !== editor.getText()) {
|
||||
editor.commands.setContent(content, false);
|
||||
}
|
||||
}, [content, editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
editor.setEditable(!readOnly);
|
||||
}, [readOnly, editor]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>{`
|
||||
.plain-text-editor p.is-empty::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
color: var(--placeholder-color, #9ca3af);
|
||||
}
|
||||
.plain-text-editor .tiptap p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
`}</style>
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className={twMerge("plain-text-editor", className)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Details hinzufügen..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Checklistenelement konnte nicht gelöscht werden"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Checklisten-Element konnte nicht aktualisiert werden"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
200
|
||||
169
|
||||
]
|
||||
],
|
||||
"translation": "Add details..."
|
||||
@@ -7332,11 +7332,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
69
|
||||
67
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
97
|
||||
95
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistNameInput.tsx",
|
||||
@@ -9571,7 +9571,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
96
|
||||
94
|
||||
]
|
||||
],
|
||||
"translation": "Unable to delete checklist item"
|
||||
@@ -9707,7 +9707,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
68
|
||||
66
|
||||
]
|
||||
],
|
||||
"translation": "Unable to update checklist item"
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Añadir detalles..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "No se pudo eliminar el elemento de la lista de verificación"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "No se pudo actualizar el elemento de la lista de verificación"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Ajouter des détails..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Impossible de supprimer l'élément de liste de contrôle"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Impossible de mettre à jour l'élément de la checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Aggiungi dettagli..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Impossibile eliminare l'elemento della checklist"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Impossibile aggiornare l'elemento della checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Voeg details toe..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Kan checklistitem niet verwijderen"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Kan checklistitem niet bijwerken"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Dodaj szczegóły..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Nie można usunąć elementu listy kontrolnej"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Nie można zaktualizować elementu listy kontrolnej"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Adicionar detalhes..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Não foi possível excluir item da checklist"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Não foi possível atualizar o item da checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Добавить детали..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -4336,8 +4336,8 @@
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -5634,7 +5634,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Не удалось удалить элемент контрольного списка"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5718,7 +5718,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Не удалось обновить элемент контрольного списка"
|
||||
},
|
||||
"fYVH36": {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { DraggableProvided } from "react-beautiful-dnd";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useEffect, useState } from "react";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import { useState } from "react";
|
||||
import { HiXMark } from "react-icons/hi2";
|
||||
import { RiDraggable } from "react-icons/ri";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import PlainTextEditor from "~/components/PlainTextEditor";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
import { invalidateCard } from "~/utils/cardInvalidation";
|
||||
@@ -33,9 +33,7 @@ export default function ChecklistItemRow({
|
||||
}: ChecklistItemRowProps) {
|
||||
const utils = api.useUtils();
|
||||
const { showPopup } = usePopup();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [completed, setCompleted] = useState(false);
|
||||
const [completed, setCompleted] = useState(item.completed);
|
||||
|
||||
const updateItem = api.checklist.updateItem.useMutation({
|
||||
onMutate: async (vars) => {
|
||||
@@ -103,21 +101,6 @@ export default function ChecklistItemRow({
|
||||
},
|
||||
});
|
||||
|
||||
// Only resync from props when switching items to avoid clobbering edits
|
||||
useEffect(() => {
|
||||
setTitle(item.title);
|
||||
setCompleted(item.completed);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.publicId]);
|
||||
|
||||
const sanitizeHtmlToPlainText = (html: string): string =>
|
||||
html
|
||||
.replace(/<br\s*\/?>(\n)?/gi, "\n")
|
||||
.replace(/<div><br\s*\/?><\/div>/gi, "")
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.trim();
|
||||
|
||||
const handleToggleCompleted = () => {
|
||||
if (viewOnly) return;
|
||||
setCompleted((prev) => !prev);
|
||||
@@ -127,14 +110,8 @@ export default function ChecklistItemRow({
|
||||
});
|
||||
};
|
||||
|
||||
const commitTitle = (rawHtml: string) => {
|
||||
if (viewOnly) return;
|
||||
const plain = sanitizeHtmlToPlainText(rawHtml);
|
||||
if (!plain || plain === item.title) {
|
||||
setTitle(item.title);
|
||||
return;
|
||||
}
|
||||
setTitle(plain);
|
||||
const commitTitle = (plain: string) => {
|
||||
if (!plain || plain === item.title) return;
|
||||
updateItem.mutate({
|
||||
checklistItemPublicId: item.publicId,
|
||||
title: plain,
|
||||
@@ -183,36 +160,26 @@ export default function ChecklistItemRow({
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="flex-1 pr-7">
|
||||
<ContentEditable
|
||||
html={title}
|
||||
disabled={viewOnly}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
// @ts-expect-error - valid event
|
||||
onBlur={(e: Event) => {
|
||||
const innerHTML = (e.target as HTMLElement).innerHTML;
|
||||
commitTitle(innerHTML);
|
||||
<PlainTextEditor
|
||||
key={item.publicId}
|
||||
content={item.title}
|
||||
readOnly={viewOnly}
|
||||
placeholder={t`Add details...`}
|
||||
onBlur={commitTitle}
|
||||
onEnter={(plain) => {
|
||||
commitTitle(plain);
|
||||
onCreateNewItem?.();
|
||||
}}
|
||||
onEscape={() => undefined}
|
||||
className={twMerge(
|
||||
"m-0 min-h-[20px] w-full p-0 text-sm leading-[20px] text-light-950 outline-none focus-visible:outline-none dark:text-dark-950",
|
||||
"m-0 min-h-[20px] w-full p-0 text-sm leading-[20px] text-light-950 dark:text-dark-950",
|
||||
viewOnly && "cursor-default",
|
||||
)}
|
||||
placeholder={t`Add details...`}
|
||||
onKeyDown={(e) => {
|
||||
if (viewOnly) return;
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const innerHTML = (e.currentTarget as HTMLElement).innerHTML;
|
||||
commitTitle(innerHTML);
|
||||
onCreateNewItem?.();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setTitle(item.title);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!viewOnly && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -39,6 +39,8 @@ services:
|
||||
|
||||
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- AXIOM_TOKEN=${AXIOM_TOKEN}
|
||||
- AXIOM_DATASET=${AXIOM_DATASET}
|
||||
|
||||
# Stripe
|
||||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||
import * as checklistRepo from "@kan/db/repository/checklist.repo";
|
||||
import { stripHtml } from "@kan/shared/utils";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { assertPermission } from "../utils/permissions";
|
||||
@@ -223,7 +224,7 @@ export const checklistRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
checklistPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500),
|
||||
title: z.string().min(1).max(500).transform(stripHtml),
|
||||
}),
|
||||
)
|
||||
.output(checklistItemSchema)
|
||||
@@ -288,7 +289,7 @@ export const checklistRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
checklistItemPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500).optional(),
|
||||
title: z.string().min(1).max(500).transform(stripHtml).optional(),
|
||||
completed: z.boolean().optional(),
|
||||
index: z.number().int().min(0).optional(),
|
||||
}),
|
||||
@@ -322,30 +323,29 @@ export const checklistRouter = createTRPCRouter({
|
||||
|
||||
const previousTitle = item.title;
|
||||
|
||||
let updatedItem;
|
||||
let updatedItem;
|
||||
|
||||
if (input.title !== undefined || input.completed !== undefined) {
|
||||
updatedItem = await checklistRepo.updateItemById(ctx.db, {
|
||||
id: item.id,
|
||||
title: input.title,
|
||||
completed: input.completed,
|
||||
});
|
||||
}
|
||||
if (input.title !== undefined || input.completed !== undefined) {
|
||||
updatedItem = await checklistRepo.updateItemById(ctx.db, {
|
||||
id: item.id,
|
||||
title: input.title,
|
||||
completed: input.completed,
|
||||
});
|
||||
}
|
||||
|
||||
if (input.index !== undefined) {
|
||||
updatedItem = await checklistRepo.reorderItem(ctx.db, {
|
||||
itemId: item.id,
|
||||
newIndex: input.index,
|
||||
});
|
||||
}
|
||||
|
||||
if (!updatedItem) {
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
}
|
||||
if (input.index !== undefined) {
|
||||
updatedItem = await checklistRepo.reorderItem(ctx.db, {
|
||||
itemId: item.id,
|
||||
newIndex: input.index,
|
||||
});
|
||||
}
|
||||
|
||||
if (!updatedItem) {
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
// Log completion toggle
|
||||
if (input.completed !== undefined) {
|
||||
@@ -371,7 +371,6 @@ export const checklistRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
return updatedItem;
|
||||
|
||||
}),
|
||||
deleteItem: protectedProcedure
|
||||
.meta({
|
||||
|
||||
@@ -19,6 +19,7 @@ import { encryptToken } from "../utils/encryption";
|
||||
export const integrationRouter = createTRPCRouter({
|
||||
saveGitHubToken: protectedProcedure
|
||||
.input(z.object({ token: z.string() }))
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
@@ -43,7 +44,9 @@ export const integrationRouter = createTRPCRouter({
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
disconnectGitHub: protectedProcedure.mutation(async ({ ctx }) => {
|
||||
disconnectGitHub: protectedProcedure
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
if (!user)
|
||||
@@ -56,7 +59,9 @@ export const integrationRouter = createTRPCRouter({
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
getGitHubStatus: protectedProcedure.query(async ({ ctx }) => {
|
||||
getGitHubStatus: protectedProcedure
|
||||
.output(z.object({ connected: z.boolean() }))
|
||||
.query(async ({ ctx }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
if (!user)
|
||||
|
||||
@@ -31,7 +31,6 @@ export const userRouter = createTRPCRouter({
|
||||
.object({
|
||||
id: z.number(),
|
||||
prefix: z.string().nullable(),
|
||||
key: z.string(),
|
||||
})
|
||||
.nullable(),
|
||||
}),
|
||||
@@ -62,7 +61,7 @@ export const userRouter = createTRPCRouter({
|
||||
return {
|
||||
...result,
|
||||
image: imageUrl,
|
||||
apiKey: apiKey ?? null,
|
||||
apiKey: apiKey ? { id: apiKey.id, prefix: apiKey.prefix } : null,
|
||||
};
|
||||
}),
|
||||
update: protectedProcedure
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
import type { NextApiRequest } from "next";
|
||||
import type { OpenApiMeta } from "trpc-to-openapi";
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||
import { env } from "next-runtime-env";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
@@ -11,7 +13,7 @@ import { initAuth } from "@kan/auth/server";
|
||||
import { createDrizzleClient } from "@kan/db/client";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("trpc");
|
||||
const log = createLogger("api");
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
@@ -50,6 +52,7 @@ interface CreateContextOptions {
|
||||
db: dbClient;
|
||||
auth: ReturnType<typeof createAuthWithHeaders>;
|
||||
headers: Headers;
|
||||
transport?: "trpc" | "rest";
|
||||
}
|
||||
|
||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
@@ -58,6 +61,8 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
db: opts.db,
|
||||
auth: opts.auth,
|
||||
headers: opts.headers,
|
||||
transport: opts.transport ?? "trpc",
|
||||
requestId: randomUUID(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,7 +74,13 @@ export const createTRPCContext = async ({ req }: CreateNextContextOptions) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
@@ -80,7 +91,13 @@ export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
@@ -97,7 +114,13 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "rest",
|
||||
});
|
||||
};
|
||||
|
||||
const t = initTRPC
|
||||
@@ -126,20 +149,39 @@ const loggingMiddleware = t.middleware(async ({ path, type, next, ctx }) => {
|
||||
const result = await next();
|
||||
const duration = Date.now() - start;
|
||||
|
||||
const meta = { procedure: path, type, duration, userId: (ctx as { user?: { id: string } }).user?.id };
|
||||
const { user, transport, requestId } = ctx as {
|
||||
user?: { id: string; email: string };
|
||||
transport?: string;
|
||||
requestId?: string;
|
||||
};
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: path,
|
||||
type,
|
||||
transport,
|
||||
duration,
|
||||
userId: user?.id,
|
||||
...(isCloud && { email: user?.email }),
|
||||
};
|
||||
|
||||
const label = transport === "rest" ? "REST" : "tRPC";
|
||||
|
||||
if (result.ok) {
|
||||
log.info(meta, "tRPC OK");
|
||||
log.info({ ...meta, status: 200 }, `${label} OK`);
|
||||
} else {
|
||||
log.error({ ...meta, err: result.error }, "tRPC error");
|
||||
const status = getHTTPStatusCodeFromError(result.error);
|
||||
const errorCode = result.error.code;
|
||||
log.error(
|
||||
{ ...meta, status, errorCode, err: result.error },
|
||||
`${label} error`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
export const publicProcedure = t.procedure.use(loggingMiddleware).meta({
|
||||
openapi: { method: "GET", path: "/public" },
|
||||
});
|
||||
export const publicProcedure = t.procedure.use(loggingMiddleware);
|
||||
|
||||
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
|
||||
if (!ctx.user) {
|
||||
@@ -163,13 +205,7 @@ const enforceUserIsAdmin = t.middleware(async ({ ctx, next }) => {
|
||||
|
||||
export const protectedProcedure = t.procedure
|
||||
.use(loggingMiddleware)
|
||||
.use(enforceUserIsAuthed)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/protected",
|
||||
},
|
||||
});
|
||||
.use(enforceUserIsAuthed);
|
||||
|
||||
export const adminProtectedProcedure = t.procedure
|
||||
.use(loggingMiddleware)
|
||||
|
||||
@@ -165,6 +165,13 @@ export function createPlugins(db: dbClient) {
|
||||
: []),
|
||||
apiKey({
|
||||
enableSessionForAPIKeys: true,
|
||||
customAPIKeyGetter: (ctx) => {
|
||||
const authorization = ctx.headers?.get("authorization");
|
||||
if (authorization?.startsWith("Bearer ")) {
|
||||
return authorization.slice(7);
|
||||
}
|
||||
return ctx.headers?.get("x-api-key") ?? undefined;
|
||||
},
|
||||
rateLimit: {
|
||||
enabled: true,
|
||||
timeWindow: 1000 * 60, // 1 minute
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@axiomhq/pino": "^1.4.0",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.3"
|
||||
},
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
import pino from "pino";
|
||||
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const level = process.env.LOG_LEVEL || (isDev ? "debug" : "info");
|
||||
|
||||
const axiomToken = process.env.AXIOM_TOKEN;
|
||||
const axiomDataset = process.env.AXIOM_DATASET;
|
||||
const useAxiom = isCloud && !!axiomToken && !!axiomDataset;
|
||||
|
||||
const targets: pino.TransportTargetOptions[] = [];
|
||||
|
||||
if (isDev) {
|
||||
targets.push({
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
ignore: "pid,hostname",
|
||||
translateTime: "HH:MM:ss",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (useAxiom) {
|
||||
targets.push({
|
||||
target: "@axiomhq/pino",
|
||||
options: {
|
||||
dataset: axiomDataset,
|
||||
token: axiomToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const logger = pino({
|
||||
level,
|
||||
...(isDev && {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
ignore: "pid,hostname",
|
||||
translateTime: "HH:MM:ss",
|
||||
},
|
||||
},
|
||||
...(targets.length > 0 && {
|
||||
transport: { targets },
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from "./email";
|
||||
export * from "./dueDateFilters";
|
||||
export * from "./s3";
|
||||
export * from "./mentions";
|
||||
export * from "./sanitize";
|
||||
|
||||
6
packages/shared/src/utils/sanitize.ts
Normal file
6
packages/shared/src/utils/sanitize.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Strips all HTML tags from a string, returning plain text.
|
||||
* Use this on any user-supplied text field before storing or displaying.
|
||||
*/
|
||||
export const stripHtml = (value: string): string =>
|
||||
value.replace(/<[^>]*>/g, "").trim();
|
||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -504,6 +504,9 @@ importers:
|
||||
|
||||
packages/logger:
|
||||
dependencies:
|
||||
'@axiomhq/pino':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
pino:
|
||||
specifier: ^9.14.0
|
||||
version: 9.14.0
|
||||
@@ -891,6 +894,14 @@ packages:
|
||||
resolution: {integrity: sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@axiomhq/js@1.4.0':
|
||||
resolution: {integrity: sha512-wC5x1ud/QJMstrjpicATkyY8+ZVWEl4WlXMtA5EZf7hkj0+b191yv4yynLxLEfr/MveXora9m6CWdJq4DsbcAg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@axiomhq/pino@1.4.0':
|
||||
resolution: {integrity: sha512-7ujZM1kqbA98BWl8ltWdTLSDE7+67nOvR30/hm8P6omISjaQV0xgtdtZtDGe/BH2VmZeQWPGfAI1FnUNYC3CtQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -4338,6 +4349,10 @@ packages:
|
||||
'@xtuc/long@4.2.2':
|
||||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
|
||||
accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -5557,6 +5572,10 @@ packages:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
@@ -5628,6 +5647,9 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-retry@6.0.0:
|
||||
resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==}
|
||||
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
@@ -7206,6 +7228,9 @@ packages:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pino-abstract-transport@1.2.0:
|
||||
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
|
||||
|
||||
@@ -7397,6 +7422,10 @@ packages:
|
||||
process-warning@5.0.0:
|
||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||
|
||||
process@0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -7600,6 +7629,10 @@ packages:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
readable-stream@4.7.0:
|
||||
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
readdirp@3.5.0:
|
||||
resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
@@ -9384,6 +9417,15 @@ snapshots:
|
||||
'@smithy/types': 4.3.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@axiomhq/js@1.4.0':
|
||||
dependencies:
|
||||
fetch-retry: 6.0.0
|
||||
|
||||
'@axiomhq/pino@1.4.0':
|
||||
dependencies:
|
||||
'@axiomhq/js': 1.4.0
|
||||
pino-abstract-transport: 1.2.0
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
@@ -12910,6 +12952,10 @@ snapshots:
|
||||
|
||||
'@xtuc/long@4.2.2': {}
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
@@ -14331,6 +14377,8 @@ snapshots:
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
execa@5.1.1:
|
||||
@@ -14403,6 +14451,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fetch-retry@6.0.0: {}
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
figures@3.2.0:
|
||||
@@ -16445,6 +16495,11 @@ snapshots:
|
||||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pino-abstract-transport@1.2.0:
|
||||
dependencies:
|
||||
readable-stream: 4.7.0
|
||||
split2: 4.2.0
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
@@ -16594,6 +16649,8 @@ snapshots:
|
||||
|
||||
process-warning@5.0.0: {}
|
||||
|
||||
process@0.11.10: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
@@ -16889,6 +16946,14 @@ snapshots:
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@4.7.0:
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
buffer: 6.0.3
|
||||
events: 3.3.0
|
||||
process: 0.11.10
|
||||
string_decoder: 1.3.0
|
||||
|
||||
readdirp@3.5.0:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
@@ -130,7 +130,9 @@
|
||||
"NOVU_API_KEY",
|
||||
"EMAIL_UNSUBSCRIBE_SECRET",
|
||||
"REDIS_URL",
|
||||
"LOG_LEVEL"
|
||||
"LOG_LEVEL",
|
||||
"AXIOM_TOKEN",
|
||||
"AXIOM_DATASET"
|
||||
],
|
||||
"globalPassThroughEnv": [
|
||||
"NODE_ENV",
|
||||
|
||||
Reference in New Issue
Block a user