Data storage (#3)

* feat: add prisma

* fix: generate prisma
This commit is contained in:
2025-06-23 19:04:01 +02:00
committed by GitHub
parent 5e5c090cd9
commit 30c67a308b
8 changed files with 189 additions and 12 deletions

View File

@@ -1,2 +1,3 @@
DATABASE_URL=""
USER_KEY="" USER_KEY=""
ANTHROPIC_API_KEY="" ANTHROPIC_API_KEY=""

View File

@@ -1,2 +1,3 @@
DATABASE_URL=""
USER_KEY="test-key-123" USER_KEY="test-key-123"
ANTHROPIC_API_KEY="" ANTHROPIC_API_KEY=""

View File

@@ -5,9 +5,10 @@
"author": "riccardo@frompixels.com", "author": "riccardo@frompixels.com",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "prisma generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "next lint --fix && prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write",
"format": "prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write", "format": "prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "jest", "test": "jest",
@@ -17,10 +18,14 @@
"analyze": "BUNDLE_ANALYZE=true next build", "analyze": "BUNDLE_ANALYZE=true next build",
"audit": "audit-ci", "audit": "audit-ci",
"vercel:link": "vercel link", "vercel:link": "vercel link",
"vercel:env": "vercel env pull .env" "vercel:env": "vercel env pull .env",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev"
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.33.1", "@anthropic-ai/sdk": "^0.33.1",
"@prisma/client": "^5.22.0",
"axios": "^1.8.2", "axios": "^1.8.2",
"next": "^15.2.4", "next": "^15.2.4",
"react": "^19.0.0", "react": "^19.0.0",
@@ -45,6 +50,7 @@
"jest": "^29.7.0", "jest": "^29.7.0",
"lint-staged": "^15.3.0", "lint-staged": "^15.3.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prisma": "^5.22.0",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.7.2" "typescript": "^5.7.2"

23
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,23 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model AnthropicQuery {
id String @id @default(cuid())
question String @db.Text
response String @db.Text
success Boolean
errorMessage String? @db.Text
tokensUsed Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("anthropic_queries")
@@index([createdAt])
@@index([success])
}

View File

@@ -1,6 +1,11 @@
import Anthropic from '@anthropic-ai/sdk'; import Anthropic from '@anthropic-ai/sdk';
export async function getMessage(text: string) { interface AnthropicResponse {
text: string;
tokensUsed: number;
}
export async function getMessage(text: string): Promise<AnthropicResponse> {
const anthropic = new Anthropic({ const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY apiKey: process.env.ANTHROPIC_API_KEY
}); });
@@ -18,7 +23,20 @@ export async function getMessage(text: string) {
try { try {
const data = response.content as [{ type: string; text: string }]; const data = response.content as [{ type: string; text: string }];
return data[0].text; const tokensUsed =
(response.usage?.input_tokens || 0) +
(response.usage?.output_tokens || 0);
console.info('Token usage:', {
input_tokens: response.usage?.input_tokens,
output_tokens: response.usage?.output_tokens,
total: tokensUsed
});
return {
text: data[0].text,
tokensUsed
};
} catch (error) { } catch (error) {
throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`); throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`);
} }

View File

@@ -1,32 +1,66 @@
import { getMessage } from '@utils/anthropicClient'; import { getMessage } from '@utils/anthropicClient';
import { ShortcutsResponse } from '../types'; import { ShortcutsResponse } from '../types';
import { dbOperations } from '@utils/db';
export async function anthropicCommand( export async function anthropicCommand(
parameters: Record<string, string> | undefined parameters: Record<string, string> | undefined
): Promise<ShortcutsResponse> { ): Promise<ShortcutsResponse> {
let question = '';
let response = '';
let success = false;
let errorMessage: string | undefined;
let tokensUsed: number | undefined;
try { try {
if (!parameters || !parameters['question']) { if (!parameters || !parameters['question']) {
errorMessage = 'Need to provide a question.';
return { return {
success: false, success: false,
message: 'Sorry. Need to provide a question.' message: 'Sorry. Need to provide a question.'
}; };
} }
const response = await getMessage( question = parameters['question'];
const prompt =
'I want to know ' + 'I want to know ' +
parameters['question'] + question +
'. Structure the response in a manner suitable for spoken communication.' '. Structure the response in a manner suitable for spoken communication.';
);
const anthropicResponse = await getMessage(prompt);
response = anthropicResponse.text;
tokensUsed = anthropicResponse.tokensUsed;
success = true;
return { return {
success: true, success: true,
message: response message: response,
data: {
tokensUsed
}
}; };
} catch (error) { } catch (error) {
console.error(error); console.error('Anthropic command error:', error);
success = false;
errorMessage = error instanceof Error ? error.message : 'Unknown error';
response = 'Sorry. There was a problem with Anthropic.';
return { return {
success: false, success: false,
message: 'Sorry. There was a problem with Anthropic.' message: response
}; };
} finally {
if (question) {
try {
await dbOperations.saveAnthropicQuery({
question,
response,
success,
errorMessage,
tokensUsed
});
} catch (error) {
console.error('Failed to log query to database:', error);
}
}
} }
} }

44
utils/db.ts Normal file
View File

@@ -0,0 +1,44 @@
import { PrismaClient } from '@prisma/client';
declare global {
var prisma: PrismaClient | undefined;
}
const db = global.prisma || new PrismaClient();
if (process.env.NODE_ENV === 'development') {
global.prisma = db;
}
export const dbOperations = {
async saveAnthropicQuery({
question,
response,
success,
errorMessage,
tokensUsed
}: {
question: string;
response: string;
success: boolean;
errorMessage?: string;
tokensUsed?: number;
}) {
try {
return await db.anthropicQuery.create({
data: {
question,
response,
success,
errorMessage,
tokensUsed
}
});
} catch (error) {
console.error('Failed to save query:', error);
return null;
}
}
};
export { db };

View File

@@ -945,6 +945,47 @@
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
"@prisma/client@^5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802"
integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==
"@prisma/debug@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412"
integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==
"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2":
version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4"
integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==
"@prisma/engines@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84"
integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==
dependencies:
"@prisma/debug" "5.22.0"
"@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
"@prisma/fetch-engine" "5.22.0"
"@prisma/get-platform" "5.22.0"
"@prisma/fetch-engine@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e"
integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==
dependencies:
"@prisma/debug" "5.22.0"
"@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
"@prisma/get-platform" "5.22.0"
"@prisma/get-platform@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd"
integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==
dependencies:
"@prisma/debug" "5.22.0"
"@rtsao/scc@^1.1.0": "@rtsao/scc@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
@@ -2648,7 +2689,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2: fsevents@2.3.3, fsevents@^2.3.2:
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@@ -4322,6 +4363,15 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
ansi-styles "^5.0.0" ansi-styles "^5.0.0"
react-is "^18.0.0" react-is "^18.0.0"
prisma@^5.22.0:
version "5.22.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197"
integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==
dependencies:
"@prisma/engines" "5.22.0"
optionalDependencies:
fsevents "2.3.3"
prompts@^2.0.1: prompts@^2.0.1:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"