content.tsx 7.3 KB

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