### Build and Start Production Server Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Commands to build the application for production and then start the production server using yarn. ```bash yarn build yarn start ``` -------------------------------- ### Start Commands for Development and Production Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Bash commands to start the application in development or production mode using environment variables. ```bash # Development NODE_ENV=development yarn dev # Production NODE_ENV=production yarn build && yarn start ``` -------------------------------- ### Database Configuration Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of setting PostgreSQL connection details in a .env file. Ensure DB_HOST, DB_NAME, DB_USERNAME, and DB_PASSWORD are provided. ```env DB_HOST=localhost DB_NAME=llm_logger DB_USERNAME=postgres DB_PASSWORD=secretpassword DB_RUN_MIGRATIONS=true ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/gnardini/llm-logger/blob/main/README.md Run this command after cloning the repository to install all necessary project dependencies using Yarn. ```bash yarn ``` -------------------------------- ### Server Configuration Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of setting server-related environment variables in a .env file. PORT and NODE_ENV are commonly used. ```env PORT=3000 NODE_ENV=development HMR_PORT=24678 ``` -------------------------------- ### Example API Key Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/ApiKeyService.md This is an example of a generated 64-character hexadecimal API key. ```plaintext 8f4a5c9e2d1b3f6a7c8e9d0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a ``` -------------------------------- ### Example .env file structure Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md This is an example of a .env file, showing common configuration variables for database, server, authentication, email, Stripe, and application settings. Values can be overridden by actual environment variables. ```bash # Database DB_HOST=localhost DB_NAME=llm_logger_dev DB_USERNAME=postgres DB_PASSWORD=dev_password DB_RUN_MIGRATIONS=true # Server PORT=3000 NODE_ENV=development HMR_PORT=24678 # Auth JWT_SECRET=dev-secret-key-change-for-production # Email (optional) RESEND_API_KEY= # Stripe (optional) STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= STRIPE_PUBLISHABLE_KEY= # App PUBLIC_APP_URL=http://localhost:3000 ``` -------------------------------- ### Install llm-logger Package Source: https://github.com/gnardini/llm-logger/blob/main/setup-libs/typescript/README.md Install the llm-logger package using npm or yarn. ```bash npm install llm-logger ``` ```bash yarn add llm-logger ``` -------------------------------- ### 404 Not Found Example Response (Log) Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example JSON response when a requested log is not found. ```json { "error": "Log not found" } ``` -------------------------------- ### Email Service (Resend) Configuration Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of setting the RESEND_API_KEY environment variable to enable email invitations via the Resend service. ```env RESEND_API_KEY=re_xyz123456789 ``` -------------------------------- ### Log Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Demonstrates how to create an instance of the Log interface with sample data. Ensure all required fields are populated. ```typescript import { Log } from '@type/log'; const log: Log = { id: 'log-uuid', organization_id: 'org-uuid', model: 'gpt-4-turbo', input_tokens: 150, output_tokens: 300, stop_reason: 'stop_sequence', tags: ['production', 'important'], user: 'user@example.com', error: null, created_at: '2024-01-20T14:30:00Z', updated_at: '2024-01-20T14:30:00Z', }; ``` -------------------------------- ### cURL Examples for Retrieving Logs Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/endpoints.md These cURL commands demonstrate how to retrieve paginated logs. Examples include fetching the first page, filtering by user, and filtering by tags. ```bash # Get page 1 of logs curl http://localhost:3000/api/logs/index?organization_id=ORG_UUID # Get logs for specific user curl http://localhost:3000/api/logs/index?organization_id=ORG_UUID&user=user%40example.com # Get logs with tags curl http://localhost:3000/api/logs/index?organization_id=ORG_UUID&tags=production,important ``` -------------------------------- ### 404 Not Found Example Response (API Key) Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example JSON response when an API key is not found or the user lacks permission to access it. ```json { "error": "API key not found or you do not have permission to delete it" } ``` -------------------------------- ### Accept Invitation Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/AuthService.md Demonstrates how to use the `signUpWithInvitation` method to accept an invitation, set authentication cookies, and handle potential errors. ```typescript import AuthService from '@backend/services/AuthService'; import { ApiError } from '@backend/core/apiHandler'; try { const result = await AuthService.signUpWithInvitation( invitationToken, 'newpassword123' ); res.cookie('token', result.token, { httpOnly: true, secure: true, path: '/', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict', }); return { user: result.user }; } catch (error) { throw new ApiError(400, 'Failed to accept invitation'); } ``` -------------------------------- ### 403 Forbidden Example Response Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md An example JSON response when a user lacks the required role for an operation. ```json { "error": "User does not have access to this organization" } ``` -------------------------------- ### FullLog Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Shows how to instantiate the FullLog type, including all fields from the base Log and the additional input/output text. All base Log fields must be present. ```typescript import { FullLog } from '@type/log'; const fullLog: FullLog = { // All Log fields id: 'log-uuid', model: 'gpt-4-turbo', input_tokens: 150, output_tokens: 300, // ... other fields // Extended fields input: 'What is machine learning?', output: 'Machine learning is a subset of artificial intelligence...', }; ``` -------------------------------- ### Create User Object Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Example of creating a User object. Ensure all required fields are populated and optional fields are handled appropriately. ```typescript import { User } from '@type/user'; const user: User = { id: 'uuid-here', email: 'user@example.com', last_access: '2024-01-20T14:30:00Z', active_org: 'org-uuid', created_at: '2024-01-15T10:00:00Z', updated_at: '2024-01-20T14:30:00Z', }; ``` -------------------------------- ### Organization Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Demonstrates how to create an instance of the Organization type. Ensure the '@type/organization' module is imported. ```typescript import { Organization } from '@type/organization'; const org: Organization = { id: 'org-uuid', name: 'Acme Corp', is_public: false, created_at: '2024-01-15T10:00:00Z', updated_at: '2024-01-15T10:00:00Z', }; ``` -------------------------------- ### Production Environment Configuration Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example .env file for the production environment. This configuration uses production-specific values for security and deployment. ```env NODE_ENV=production DB_HOST=prod-db.example.com DB_NAME=llm_logger_prod DB_USERNAME=prod_user DB_PASSWORD=strong-password DB_RUN_MIGRATIONS=false PORT=3000 JWT_SECRET=long-random-production-secret PUBLIC_APP_URL=https://llmlogger.com RESEND_API_KEY=re_prod_key STRIPE_SECRET_KEY=sk_live_xxx STRIPE_WEBHOOK_SECRET=whsec_live_xxx STRIPE_PUBLISHABLE_KEY=pk_live_xxx ``` -------------------------------- ### OrgUser Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Shows how to instantiate the OrgUser type. The '@type/organization' module must be imported, and a valid MembershipType should be provided. ```typescript import { OrgUser } from '@type/organization'; const member: OrgUser = { id: 'member-id', organization_id: 'org-uuid', user_id: 'user-uuid', email: 'user@example.com', membership_type: 'member', created_at: '2024-01-20T10:00:00Z', updated_at: '2024-01-20T10:00:00Z', }; ``` -------------------------------- ### Generate Invitation Link Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of how to construct an invitation link using the PUBLIC_APP_URL configuration variable. ```typescript import { PUBLIC_APP_URL } from '@backend/config'; const invitationLink = `${PUBLIC_APP_URL}/welcome?token=${token}`; ``` -------------------------------- ### Start Project in Production Mode Source: https://github.com/gnardini/llm-logger/blob/main/README.md Launches the LLM Logger project in production mode after the build process is complete. Ensure the .env file is configured for production. ```bash yarn start ``` -------------------------------- ### 500 Internal Server Error Example Response Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example JSON response for any unhandled exceptions, indicating a server-side issue. ```json { "error": "Internal Server Error" } ``` -------------------------------- ### Development Environment Configuration Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example .env file for the development environment. This configuration is typically used during local development and testing. ```env NODE_ENV=development DB_HOST=localhost DB_NAME=llm_logger_dev DB_USERNAME=postgres DB_PASSWORD=postgres DB_RUN_MIGRATIONS=true PORT=3000 JWT_SECRET=dev-secret-key PUBLIC_APP_URL=http://localhost:3000 RESEND_API_KEY= STRIPE_SECRET_KEY=sk_test_xxx STRIPE_WEBHOOK_SECRET=whsec_test_xxx ``` -------------------------------- ### JWT Secret Configuration Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of setting the JWT_SECRET environment variable. This key is crucial for signing and verifying JSON Web Tokens. ```env JWT_SECRET=your-super-secret-key-change-this-in-production ``` -------------------------------- ### API Key Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Demonstrates how to create a new API key and retrieve existing keys for an organization. Note that the full key is only available upon creation; subsequent retrievals show a redacted version. ```typescript import { ApiKey } from '@type/apiKey'; import ApiKeyService from '@backend/services/ApiKeyService'; // Create new key const newKey: ApiKey = await ApiKeyService.createApiKey( organizationId, 'Production API Key' ); console.log(`Full key: ${newKey.key}`); // Shown once at creation // Retrieve keys (redacted) const keys: ApiKey[] = await ApiKeyService.getApiKeysForOrganization( organizationId ); keys.forEach(k => { console.log(`${k.name}: ${k.key}`); // e.g., "Production API Key: 8f4a...8f9a" }); ``` -------------------------------- ### Check Required Environment Variables Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Ensures that all necessary environment variables are set before the server starts. Throws an error if any are missing. ```typescript const required = ['JWT_SECRET', 'DB_PASSWORD']; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error(`Missing required environment variables: ${missing.join(', ')}`); } ``` -------------------------------- ### Run Project in Development Mode Source: https://github.com/gnardini/llm-logger/blob/main/README.md Starts the LLM Logger project in development mode. The server typically runs on http://localhost:3000, but this can be configured via the .env file. ```bash yarn dev ``` -------------------------------- ### HTML Email Body Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md This TypeScript code demonstrates an HTML email body with basic formatting. ```typescript // Better: HTML with formatting const body = `

Hi!

You've been invited...

See you soon!

`; // Avoid: Complex HTML with external resources ``` -------------------------------- ### Throw API Errors - Usage Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/apiHandler.md Demonstrates how to throw ApiError instances for common scenarios like permission denied or resource not found. Ensure ApiError is imported before use. ```typescript import { ApiError } from '@backend/core/apiHandler'; // Throw a 403 Forbidden error if (!hasPermission) { throw new ApiError(403, 'User does not have permission'); } // Throw a 404 Not Found error if (!resource) { throw new ApiError(404, 'Resource not found'); } ``` -------------------------------- ### Database Configuration Usage in TypeScript Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Import and use database configuration variables in your TypeScript code. Requires installation of '@backend/config'. ```typescript import { DB_HOST, DB_NAME, DB_USERNAME, DB_PASSWORD, DB_RUN_MIGRATIONS, } from '@backend/config'; const knexDb = knex({ client: 'pg', connection: { host: DB_HOST, database: DB_NAME, user: DB_USERNAME, password: DB_PASSWORD, }, }); ``` -------------------------------- ### Get or Create User by Email Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/UsersService.md Fetches a user by email or creates a new one if not found. Used in conjunction with organization membership. ```typescript const { user } = await UsersService.getOrCreateUserByEmail(email); await OrganizationMembersService.getOrCreateOrganizationMember( organizationId, user.id, 'member' ); ``` -------------------------------- ### cURL Example for Creating LLM Log Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/endpoints.md This cURL command demonstrates how to send a POST request to create a new LLM log entry. Ensure you replace 'YOUR_API_KEY' with your actual API key and adjust the payload as needed. ```bash curl -X POST http://localhost:3000/api/logs/create \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4-turbo", "input_tokens": 150, "output_tokens": 300, "input": "What is AI?", "output": "Artificial Intelligence is...", "tags": ["production"] }' ``` -------------------------------- ### Forward Stripe Events Locally Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Use the Stripe CLI to forward events to your local development server. Ensure you have installed and logged in with the Stripe CLI. ```bash # Install Stripe CLI brew install stripe/stripe-cli/stripe # Login stripe login # Forward events to local server stripe listen --forward-to localhost:3000/api/stripe/webhook # Trigger test events stripe trigger payment_intent.succeeded ``` -------------------------------- ### Plain Text Email Body Example Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md This TypeScript code shows a simple and readable plain text email body. ```typescript // Good: Plain text is readable const body = `Hi! You've been invited... See you soon!`; ``` -------------------------------- ### Create Authenticated API Handler - POST Create API Key Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/apiHandler.md Example of creating a POST API endpoint that requires user authentication to create an API key. It demonstrates checking user permissions against organization ownership before proceeding. ```typescript import { createApiHandler, ApiError } from '@backend/core/apiHandler'; import { createApiKeySchema } from '@backend/schemas/apiKey'; import ApiKeyService from '@backend/services/ApiKeyService'; import OrganizationsService from '@backend/services/OrganizationsService'; export default createApiHandler({ method: 'POST', schema: createApiKeySchema, requiresAuth: true, // User must be authenticated handler: async (data, { user }) => { // user is guaranteed to be User (not null) when requiresAuth: true const hasAccess = await OrganizationsService.userOwnsOrganization( user.id, data.organization_id ); if (!hasAccess) { throw new ApiError(403, 'User does not have permission'); } const apiKey = await ApiKeyService.createApiKey( data.organization_id, data.name ); return apiKey; }, }); ``` -------------------------------- ### Create Basic API Handler - POST Login Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/apiHandler.md Example of creating a POST API endpoint for user login using createApiHandler. It includes input validation via a Zod schema and sets an HTTP-only cookie upon successful login. ```typescript import { createApiHandler, ApiError } from '@backend/core/apiHandler'; import { loginSchema } from '@backend/schemas/auth'; import AuthService from '@backend/services/AuthService'; export default createApiHandler({ method: 'POST', schema: loginSchema, handler: async (data, { res }) => { const result = await AuthService.logIn(data.email, data.password); if (!result) { throw new ApiError(400, 'Invalid email or password'); } res.cookie('token', result.token, { httpOnly: true, secure: true, path: '/', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict', }); return { user: result.user }; }, }); ``` -------------------------------- ### Get or Create User by Email (TypeScript) Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/UsersService.md Use this method to retrieve an existing user by their email address or create a new user if one does not exist. It does not set a password or last access time for newly created users. ```typescript static async getOrCreateUserByEmail( email: string ): Promise<{ user: User; created: boolean }> ``` ```typescript import { UsersService } from '@backend/services/UsersService'; // Ensure user exists (creates if not) const { user, created } = await UsersService.getOrCreateUserByEmail( 'member@example.com' ); if (created) { console.log('New user created, invite needed'); } else { console.log('Existing user found'); } // Then add to organization ``` -------------------------------- ### 405 Method Not Allowed Example Response Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example JSON response when an invalid HTTP method is used for a request. ```json { "error": "Method Not Allowed" } ``` -------------------------------- ### Build Project for Production Source: https://github.com/gnardini/llm-logger/blob/main/README.md Compiles the project for production deployment. This command should be run after setting NODE_ENV=production in the .env file. ```bash yarn build ``` -------------------------------- ### signUp Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/AuthService.md Creates a new user account with the provided email and password. It returns the newly created user object and a JWT token. ```APIDOC ## signUp ### Description Creates a new user account with email and password. ### Method `static signUp(email: string, password: string): Promise<{ user: User; token: string }>` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```typescript import AuthService from '@backend/services/AuthService'; import { ApiError } from '@backend/core/apiHandler'; try { const result = await AuthService.signUp('newuser@example.com', 'password123'); res.cookie('token', result.token, { httpOnly: true, secure: true, path: '/', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict', }); return { user: result.user }; } catch (error) { if (error instanceof Error && error.message.includes('duplicate key value violates unique constraint')) { throw new ApiError(400, 'Email already exists'); } throw error; } ``` ### Response #### Success Response (200) - **user** (User): Newly created user object. - **token** (string): JWT token valid for 30 days. #### Response Example ```json { "user": { ... }, "token": "your_jwt_token" } ``` ### Throws Throws error if email already exists (duplicate key constraint violation). ``` -------------------------------- ### Get Organizations and Active Org Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/UsersService.md Retrieves a user's organizations and their currently active organization. Useful for navigation. ```typescript const { organizations, activeOrg } = await UsersService.getOrganizationsAndActive(user); // Display in sidebar organizations.forEach(org => { console.log(org.name); // Organization name }); ``` -------------------------------- ### Initialize Database with Migrations Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Manually triggers database initialization and runs pending migrations. Ensure the `initDatabase` function is imported. ```typescript import { initDatabase } from '@backend/db/db'; await initDatabase(true); // Run migrations ``` -------------------------------- ### Get Organization Members Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationMembersService.md Retrieves all members of a specified organization. Requires the organization's UUID. Propagates database errors. ```typescript import OrganizationMembersService from '@backend/services/OrganizationMembersService'; const members = await OrganizationMembersService.getOrganizationMembers(orgId); members.forEach(member => { console.log(`${member.email} - ${member.membership_type}`); }); // Output: // admin@example.com - owner // user@example.com - member // dev@example.com - admin ``` -------------------------------- ### signUpWithInvitation Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/AuthService.md Creates a new user or updates an existing user via an invitation token, assigning them to an organization. ```APIDOC ## signUpWithInvitation Creates a new user or updates existing user via invitation token, assigns to organization. ### Parameters - **token** (string) - Required - Valid invitation JWT token - **password** (string) - Required - Password for user account (minimum 6 characters) ### Return Returns object with: - `user`: Created or updated user object - `token`: New JWT auth token valid for 30 days ### Throws - Throws "Invalid or expired invitation token" if token verification fails - Propagates database errors ### Usage ```typescript import AuthService from '@backend/services/AuthService'; import { ApiError } from '@backend/core/apiHandler'; try { const result = await AuthService.signUpWithInvitation( invitationToken, 'newpassword123' ); res.cookie('token', result.token, { httpOnly: true, secure: true, path: '/', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict', }); return { user: result.user }; } catch (error) { throw new ApiError(400, 'Failed to accept invitation'); } ``` ``` -------------------------------- ### Resend Instance Configuration Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md Initializes the Resend client using an API key from environment configuration. If the API key is not found, the resend instance will be null. ```typescript import { Resend } from 'resend'; import { RESEND_API_KEY } from '@backend/config'; const resend = RESEND_API_KEY ? new Resend(RESEND_API_KEY) : null; ``` -------------------------------- ### JWT Authentication Usage in TypeScript Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Example of using JWT_SECRET to sign and verify authentication tokens in TypeScript. Requires the 'jsonwebtoken' library. ```typescript import { JWT_SECRET } from '@backend/config'; import jwt from 'jsonwebtoken'; // Create token const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '30d', }); // Verify token const decoded = jwt.verify(token, JWT_SECRET); ``` -------------------------------- ### 500 Internal Server Error Logged To Console Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example of how an internal server error is logged to the console, including the original error message. ```text Error in API handler: [original error message] ``` -------------------------------- ### Manual Testing of Email Invitations Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md This bash script demonstrates how to manually test email invitations by setting up the environment, running the application, and sending an invitation via curl. ```bash # Create test .env with RESEND_API_KEY RESEND_API_KEY=re_test_key # Run application yarn dev # Invite user via API curl -X POST http://localhost:3000/api/organizations/members/add \ -H "Cookie: token=YOUR_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "organizationId": "org-uuid", "email": "newemail@example.com", "membershipType": "member" }' # Check console for "Email sent successfully!" message ``` -------------------------------- ### Import and Initialize LLMLogger Source: https://github.com/gnardini/llm-logger/blob/main/setup-libs/typescript/README.md Import the LLMLogger class and create an instance with your API key. ```typescript import { LLMLogger } from 'llm-logger'; ``` ```typescript const logger = new LLMLogger({ apiKey: 'your-api-key-here' }); ``` -------------------------------- ### Get Membership Type Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationMembersService.md Retrieves the role or membership type of a user within a specific organization. Returns 'none' if the user is not a member. ```typescript static getMembershipType( organizationId: string, userId: string ): Promise ``` ```typescript import OrganizationMembersService from '@backend/services/OrganizationMembersService'; const role = await OrganizationMembersService.getMembershipType(orgId, userId); if (role === 'none') { console.log('User is not a member'); } else { console.log(`User role: ${role}`); } ``` -------------------------------- ### Backend Project Structure Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/00_START_HERE.md Illustrates the directory layout of the backend services, including endpoint handlers, business logic, schemas, core framework, and database components. Also shows the location of TypeScript interfaces. ```tree backend/ ├── api/ → Endpoint handlers ├── services/ → Business logic (9 services) ├── schemas/ → Zod validation ├── core/ → Framework & auth └── db/ → Database & migrations types/ → TypeScript interfaces (11 types) ``` -------------------------------- ### Sign Up New User with AuthService Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/AuthService.md Creates a new user account with the provided email and password. Returns the newly created user object and a JWT token. Handles existing email errors by throwing an ApiError. The password is automatically hashed using bcrypt with 10 salt rounds. ```typescript import AuthService from '@backend/services/AuthService'; import { ApiError } from '@backend/core/apiHandler'; try { const result = await AuthService.signUp('newuser@example.com', 'password123'); res.cookie('token', result.token, { httpOnly: true, secure: true, path: '/', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict', }); return { user: result.user }; } catch (error) { if (error instanceof Error && error.message.includes('duplicate key value violates unique constraint')) { throw new ApiError(400, 'Email already exists'); } throw error; } ``` -------------------------------- ### Get Organization by API Key Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/ApiKeyService.md Looks up an organization using an API key. Updates the last used timestamp for the key. Does not throw on invalid keys. ```typescript static getOrganizationByApiKey( apiKey: string ): Promise ``` ``` ```typescript import ApiKeyService from '@backend/services/ApiKeyService'; import { ApiError } from '@backend/core/apiHandler'; // In log creation endpoint const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { throw new ApiError(401, 'Missing Authorization header'); } const apiKey = authHeader.substring(7); const organization = await ApiKeyService.getOrganizationByApiKey(apiKey); if (!organization) { throw new ApiError(401, 'Invalid API key'); } // Now safe to create log for this organization ``` -------------------------------- ### Create Log Entry Usage Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/types.md Demonstrates how to construct and use the CreateLogInput type with LogService to create a new log entry. Ensure necessary imports are included. ```typescript import { CreateLogInput } from '@type/log'; import LogService from '@backend/services/LogService'; const logData: CreateLogInput = { organization_id: 'org-uuid', model: 'gpt-4-turbo', input_tokens: 150, output_tokens: 300, input: 'What is AI?', output: 'Artificial Intelligence is...', tags: ['qa', 'production'], user: 'user@example.com', }; const created = await LogService.createLog(logData); ``` -------------------------------- ### Modify Sender Address Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md Example of how to change the sender's email address and name in the email sending configuration. Ensure the domain is verified in the Resend dashboard. ```typescript await resend.emails.send({ from: 'Your Name ', to, subject, html: body, }); ``` -------------------------------- ### Integrate Stripe Webhook Handler with Express Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/StripeService.md Example of how to integrate the StripeService.handleWebhook method into an Express.js API route. Ensure the raw body is parsed for signature verification. ```typescript import { StripeService } from '@backend/services/StripeService'; import express from 'express'; // In API router router.post( '/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature'] as string; try { await StripeService.handleWebhook(req.body, sig); res.sendStatus(200); } catch (err: any) { console.error('Error processing webhook:', err); res.sendStatus(500); } } ); ``` -------------------------------- ### Initialize Stripe Client Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/StripeService.md Initializes the Stripe client with the secret key and API version. Ensure STRIPE_SECRET_KEY is set in your environment variables. ```typescript const stripe = new Stripe(STRIPE_SECRET_KEY, { apiVersion: '2024-06-20', }); ``` -------------------------------- ### Check for Missing or Invalid Authorization Header Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Validates the presence and format of the Authorization header. Throws an ApiError with a 401 status if the header is missing or does not start with 'Bearer '. ```typescript const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new ApiError(401, 'Missing or invalid Authorization header'); } ``` -------------------------------- ### Copy Environment Template Source: https://github.com/gnardini/llm-logger/blob/main/README.md Copies the template environment file to a new file named .env. This is the first step in configuring the project's environment variables. ```bash cp .env.template .env ``` -------------------------------- ### Log OpenAI API Calls Source: https://github.com/gnardini/llm-logger/blob/main/setup-libs/typescript/README.md Log OpenAI chat completion calls by passing messages and the completion object to logger.openaiLog. Ensure the openai library is installed and configured. ```typescript import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: 'your-openai-api-key', }); const messages = [{ role: 'user', content: 'Hello, how are you?' }] const completion = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages, }); await logger.openaiLog(messages, chatCompletion, ['openai-example'], null); ``` -------------------------------- ### Import Configuration in Backend Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Import specific configuration variables from the '@backend/config' module. Ensure these variables are defined in your configuration file. ```typescript import { PORT, NODE_ENV, IS_DEV_MODE, JWT_SECRET, DB_HOST, DB_NAME, DB_USERNAME, DB_PASSWORD, DB_RUN_MIGRATIONS, RESEND_API_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PUBLISHABLE_KEY, PUBLIC_APP_URL, } from '@backend/config'; ``` -------------------------------- ### Create Log with Tags Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/LogService.md Create a new log entry and assign tags using a PostgreSQL array. This allows for efficient filtering of logs based on multiple criteria. ```typescript const log = await LogService.createLog({ // ... other fields tags: ['important', 'production', 'v1.0'], }); ``` -------------------------------- ### Process Checkout Session Completed Event Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/StripeService.md Example implementation for handling the 'checkout.session.completed' event. Extracts user ID from metadata and includes placeholders for payment handling logic. ```typescript if (event.type === 'checkout.session.completed') { const session = event.data.object as Stripe.Checkout.Session; const userId = session.metadata?.user_id; if (!userId) { throw new Error('Missing user_id in session metadata'); } // TODO: Implement your payment handling: // - Update user subscription status // - Send confirmation email // - Create invoice record // - Grant access to premium features // - Log transaction for analytics } ``` -------------------------------- ### Configure Stripe API Keys Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Set these environment variables to enable Stripe payment processing. Required only if payment features are enabled. ```env STRIPE_SECRET_KEY=sk_test_xyz123456789 STRIPE_WEBHOOK_SECRET=whsec_xyz123456789 STRIPE_PUBLISHABLE_KEY=pk_test_xyz123456789 ``` -------------------------------- ### createOrganization Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationsService.md Creates a new organization and assigns the creating user as its owner. It returns the newly created Organization object. ```APIDOC ## createOrganization ### Description Creates a new organization and adds the creating user as owner. ### Method `static createOrganization(name: string, userId: string): Promise` ### Parameters #### Path Parameters - **name** (string) - Required - Organization name - **userId** (string) - Required - User UUID of the creator ### Return Returns `Organization` object: - `id`: Generated UUID v7 - `name`: Organization name - `is_public`: false (default) - `created_at`: ISO 8601 timestamp - `updated_at`: ISO 8601 timestamp ### Side Effects - Creates organization record - Creates user_organizations record with membership_type 'owner' - User becomes the sole owner of the organization ### Throws Propagates database errors. ### Usage ```typescript import OrganizationsService from '@backend/services/OrganizationsService'; const org = await OrganizationsService.createOrganization( 'Acme Corp', userId ); console.log(org.id); // Organization UUID console.log(org.name); // 'Acme Corp' ``` ``` -------------------------------- ### Get Organization by Name Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationsService.md Retrieves an organization by its exact, case-sensitive name. Use this when you need to fetch a specific organization's details using its name. Ensure the OrganizationsService is imported. ```typescript static getOrganizationByName(name: string): Promise ``` ```typescript import OrganizationsService from '@backend/services/OrganizationsService'; const org = await OrganizationsService.getOrganizationByName('Acme Corp'); if (org) { console.log(`Found: ${org.id}`); } ``` -------------------------------- ### Get API Keys for Organization Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/ApiKeyService.md Retrieves all API keys for a given organization. API key values are automatically redacted for security, showing only the first and last 4 characters. ```typescript static getApiKeysForOrganization( organizationId: string ): Promise ``` ``` ```typescript import ApiKeyService from '@backend/services/ApiKeyService'; const apiKeys = await ApiKeyService.getApiKeysForOrganization(organizationId); // Display to user apiKeys.forEach(key => { console.log(`${key.name}: ${key.key}`); console.log(` Created: ${key.created_at}`); console.log(` Last used: ${key.last_used_at || 'Never'}`); }); // Output: // Production API Key: 8f4a...8f9a // Created: 2024-01-15T10:30:00Z // Last used: 2024-01-20T14:45:30Z ``` -------------------------------- ### Trigger Business Logic Error for Duplicate Email Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/errors.md Example of how to throw an ApiError with a 400 status code when a duplicate email is detected during signup. This check is based on database constraint violation messages. ```typescript if (error instanceof Error && error.message.includes('duplicate key value violates unique constraint')) { throw new ApiError(400, 'Email already exists'); } ``` -------------------------------- ### Create New Organization Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/INDEX.md Create a new organization with a given name. The user initiating the creation automatically becomes the owner. ```typescript const org = await OrganizationsService.createOrganization( 'Company Name', userId ); // Creator becomes owner ``` -------------------------------- ### Get Organizations for User - TypeScript Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationsService.md Retrieve all organizations a user is a member of. This method takes a user ID and returns an array of organization objects. If the user is not a member of any organization, an empty array is returned. ```typescript static getOrganizationsForUser(userId: string): Promise ``` ```typescript import OrganizationsService from '@backend/services/OrganizationsService'; const organizations = await OrganizationsService.getOrganizationsForUser(userId); organizations.forEach(org => { console.log(`${org.name} (${org.id})`); }); ``` -------------------------------- ### Get or Create Organization Member Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/OrganizationMembersService.md Adds a user to an organization or updates their role if they are already a member. This function can create a new membership record or update an existing one, propagating database errors. ```typescript static getOrCreateOrganizationMember( organizationId: string, userId: string, membershipType: 'admin' | 'member' ): Promise ``` ```typescript import OrganizationMembersService from '@backend/services/OrganizationMembersService'; // Add user to organization as member const member = await OrganizationMembersService.getOrCreateOrganizationMember( organizationId, userId, 'member' ); // Update user to admin const updated = await OrganizationMembersService.getOrCreateOrganizationMember( organizationId, userId, 'admin' ); ``` -------------------------------- ### getOrganizationsAndActive Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/UsersService.md Retrieves all organizations for a user, determines the active organization, and gets the user's membership type. It handles logic for selecting the active organization based on provided parameters or user settings. ```APIDOC ## getOrganizationsAndActive ### Description Retrieves all organizations for a user, determines active organization, and gets membership type. ### Method `static async getOrganizationsAndActive(user: User, queryOrgId?: string): Promise<{ organizations: Organization[]; activeOrg: Organization; membershipType: MembershipType; }>` ### Parameters #### Path Parameters - **user** (User) - Required - Current user object - **queryOrgId** (string) - Optional - Optional organization UUID from query parameter ### Return Returns object with: - `organizations`: Array of all organizations user is member of - `activeOrg`: Currently active organization - `membershipType`: User's role in active organization ### Active Organization Logic Determines active organization in this priority order: 1. If `queryOrgId` provided and user is member: use it 2. If user's `active_org` is set: use it 3. Otherwise: use first organization ### Side Effects - If active organization changed, updates user's `active_org` field - Does NOT update user's `active_org` if it matches the determined active organization ### Throws Propagates database errors. ### Usage ```typescript import { UsersService } from '@backend/services/UsersService'; // Get user's organizations and current context const { organizations, activeOrg, membershipType } = await UsersService.getOrganizationsAndActive(user, queryOrgId); // Response includes context for frontend return { organizations, activeOrg, membershipType, }; ``` ### Typical Usage in Endpoints ```typescript import { UsersService } from '@backend/services/UsersService'; import { createApiHandler } from '@backend/core/apiHandler'; export default createApiHandler({ method: 'GET', schema: getContextSchema, requiresAuth: true, handler: async (data, { user }) => { // Get organizations and active org for sidebar navigation const { organizations, activeOrg, membershipType } = await UsersService.getOrganizationsAndActive( user, data.org_id // Optional: user selected org from query ); return { organizations, activeOrg, membershipType, }; }, }); ``` ``` -------------------------------- ### Create Log with Token Counts Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/LogService.md Create a new log entry, populating input and output token counts from an LLM API response. Ensure the response object has a `usage` property with `prompt_tokens` and `completion_tokens`, and `choices` with the content. ```typescript const log = await LogService.createLog({ organization_id: org.id, model: 'gpt-4', input_tokens: response.usage.prompt_tokens, output_tokens: response.usage.completion_tokens, input: prompt, output: response.choices[0].message.content, }); ``` -------------------------------- ### Database Connection Pool Settings (Default) Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/configuration.md Configures the database connection pool with a minimum of 0 and a maximum of 20 connections. Suitable for serverless environments. ```typescript pool: { min: 0, max: 20, } ``` -------------------------------- ### Send Email with EmailService Source: https://github.com/gnardini/llm-logger/blob/main/_autodocs/api-reference/EmailService.md Use this method to send simple or HTML emails. Ensure the RESEND_API_KEY environment variable is configured. The 'from' address is hardcoded. ```typescript import { EmailService } from '@backend/services/EmailService'; // Send simple email await EmailService.sendEmail( 'user@example.com', 'Welcome to LLM Logger', '

Welcome!

Your account has been created.

' ); // Send HTML email const htmlBody = `

Invitation

You've been invited to join our team.

Accept Invitation `; await EmailService.sendEmail( 'newuser@example.com', 'Join Our Team', htmlBody ); ```