content.tsx264 lines · main
1import { useEffect, useMemo, useState } from 'react'
2import { CodeBlock } from 'ui-patterns/CodeBlock'
3import { MultipleCodeBlock } from 'ui-patterns/MultipleCodeBlock'
4import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
5
6import {
7 type ConnectionStringMethod,
8 type DatabaseConnectionType,
9} from '@/components/interfaces/ConnectSheet/Connect.constants'
10import type { StepContentProps } from '@/components/interfaces/ConnectSheet/Connect.types'
11import { ConnectionParameters } from '@/components/interfaces/ConnectSheet/ConnectionParameters'
12import {
13 buildConnectionParameters,
14 buildSafeConnectionString,
15 parseConnectionParams,
16 PASSWORD_PLACEHOLDER,
17 resolveConnectionString,
18} from '@/components/interfaces/ConnectSheet/ConnectionString.utils'
19
20const DOTNET_CONFIG_COMMAND =
21 'dotnet add package Microsoft.Extensions.Configuration.Json --version YOUR_DOTNET_VERSION'
22
23type DirectFilesConfig = {
24 files: {
25 name: string
26 language?: string
27 code: string
28 }[]
29 connectionStringFile?: string
30 postCommands?: { label: string; command: string }[]
31}
32
33function DirectFilesContent({ state, connectionStringPooler }: StepContentProps) {
34 const connectionType = (state.connectionType as DatabaseConnectionType) ?? 'uri'
35 const connectionMethod = (state.connectionMethod as ConnectionStringMethod) ?? 'direct'
36 const useSharedPooler = Boolean(state.useSharedPooler)
37
38 const resolvedConnectionString = useMemo(
39 () =>
40 resolveConnectionString({
41 connectionMethod,
42 useSharedPooler,
43 connectionStringPooler,
44 }),
45 [connectionMethod, useSharedPooler, connectionStringPooler]
46 )
47
48 const connectionParams = useMemo(
49 () => parseConnectionParams(resolvedConnectionString),
50 [resolvedConnectionString]
51 )
52
53 const safeConnectionString = useMemo(
54 () => buildSafeConnectionString(resolvedConnectionString, connectionParams),
55 [resolvedConnectionString, connectionParams]
56 )
57
58 const config: DirectFilesConfig | null = useMemo(() => {
59 const envFile = {
60 name: '.env',
61 language: 'bash',
62 code: `DATABASE_URL=${safeConnectionString}`,
63 }
64
65 switch (connectionType) {
66 case 'nodejs':
67 return {
68 files: [
69 {
70 name: 'db.js',
71 language: 'js',
72 code: `import postgres from 'postgres'
73
74const connectionString = process.env.DATABASE_URL
75const sql = postgres(connectionString)
76
77export default sql`,
78 },
79 envFile,
80 ],
81 connectionStringFile: envFile.name,
82 }
83
84 case 'golang':
85 return {
86 files: [
87 {
88 name: 'main.go',
89 language: 'go',
90 code: `package main
91
92import (
93\t"context"
94\t"log"
95\t"os"
96\t"github.com/jackc/pgx/v5"
97)
98
99func main() {
100\tconn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
101\tif err != nil {
102\t\tlog.Fatalf("Failed to connect to the database: %v", err)
103\t}
104\tdefer conn.Close(context.Background())
105
106\t// Example query to test connection
107\tvar version string
108\tif err := conn.QueryRow(context.Background(), "SELECT version()").Scan(&version); err != nil {
109\t\tlog.Fatalf("Query failed: %v", err)
110\t}
111
112\tlog.Println("Connected to:", version)
113}`,
114 },
115 envFile,
116 ],
117 connectionStringFile: envFile.name,
118 }
119
120 case 'dotnet':
121 return {
122 files: [
123 {
124 name: 'appsettings.json',
125 language: 'json',
126 code: `{
127 "ConnectionStrings": {
128 "DefaultConnection": "Host=${connectionParams.host};Database=${connectionParams.database};Username=${connectionParams.user};Password=${PASSWORD_PLACEHOLDER};SSL Mode=Require;Trust Server Certificate=true"
129 }
130}`,
131 },
132 ],
133 connectionStringFile: 'appsettings.json',
134 postCommands: [
135 {
136 label: 'Add the configuration package to read the settings.',
137 command: DOTNET_CONFIG_COMMAND,
138 },
139 ],
140 }
141
142 case 'python':
143 return {
144 files: [
145 {
146 name: 'main.py',
147 language: 'python',
148 code: `import psycopg2
149from dotenv import load_dotenv
150import os
151
152# Load environment variables from .env
153load_dotenv()
154
155# Fetch variables
156DATABASE_URL = os.getenv("DATABASE_URL")
157
158# Connect to the database
159connection = psycopg2.connect(DATABASE_URL)`,
160 },
161 envFile,
162 ],
163 connectionStringFile: envFile.name,
164 }
165
166 case 'sqlalchemy':
167 return {
168 files: [
169 {
170 name: 'main.py',
171 language: 'python',
172 code: `from sqlalchemy import create_engine
173# from sqlalchemy.pool import NullPool
174from dotenv import load_dotenv
175import os
176
177# Load environment variables from .env
178load_dotenv()
179
180# Fetch variables
181USER = os.getenv("user")
182PASSWORD = os.getenv("password")
183HOST = os.getenv("host")
184PORT = os.getenv("port")
185DBNAME = os.getenv("dbname")
186
187# Construct the SQLAlchemy connection string
188DATABASE_URL = f"postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?sslmode=require"
189
190# Create the SQLAlchemy engine
191engine = create_engine(DATABASE_URL)
192# If using Transaction Pooler or Session Pooler, we want to ensure we disable SQLAlchemy client side pooling -
193# https://docs.sqlalchemy.org/en/20/core/pooling.html#switching-pool-implementations
194# engine = create_engine(DATABASE_URL, poolclass=NullPool)
195
196# Test the connection
197try:
198 with engine.connect() as connection:
199 print("Connection successful!")
200except Exception as e:
201 print(f"Failed to connect: {e}")`,
202 },
203 {
204 name: '.env',
205 language: 'bash',
206 code: [
207 `user=${connectionParams.user}`,
208 `password=${PASSWORD_PLACEHOLDER}`,
209 `host=${connectionParams.host}`,
210 `port=${connectionParams.port}`,
211 `dbname=${connectionParams.database}`,
212 ].join('\n'),
213 },
214 ],
215 connectionStringFile: '.env',
216 }
217
218 default:
219 return null
220 }
221 }, [connectionType, safeConnectionString, connectionParams])
222
223 const defaultFile = config?.files[0]?.name ?? ''
224 const [activeFile, setActiveFile] = useState(defaultFile)
225
226 useEffect(() => {
227 setActiveFile(defaultFile)
228 }, [connectionType, defaultFile])
229
230 if (!resolvedConnectionString) {
231 return (
232 <div className="p-4">
233 <GenericSkeletonLoader />
234 </div>
235 )
236 }
237
238 if (!config?.files.length) {
239 return null
240 }
241
242 return (
243 <div className="flex flex-col gap-3">
244 <MultipleCodeBlock files={config.files} value={activeFile} onValueChange={setActiveFile} />
245 <ConnectionParameters parameters={buildConnectionParameters(connectionParams)} />
246 {(config.postCommands ?? []).map((command) => (
247 <div key={command.command} className="flex flex-col gap-2">
248 <p className="text-sm text-foreground-light">{command.label}</p>
249 <CodeBlock
250 className="[&_code]:text-foreground"
251 wrapperClassName="lg:col-span-2"
252 value={command.command}
253 hideLineNumbers
254 language="bash"
255 >
256 {command.command}
257 </CodeBlock>
258 </div>
259 ))}
260 </div>
261 )
262}
263
264export default DirectFilesContent