From d6f1e08b9ae19a7b878e29ebf27e8cc6907a4190 Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Thu, 24 Aug 2023 07:50:20 +0100 Subject: [PATCH 1/7] generate delete component --- src/components/index.ts | 145 +++++++++++++++++++++++++++++++++++++++- src/hooks/joinTable.ts | 3 +- src/hooks/table.ts | 11 +-- src/hooks/view.ts | 3 +- src/metadata.ts | 3 +- src/types.ts | 3 +- 6 files changed, 158 insertions(+), 10 deletions(-) diff --git a/src/components/index.ts b/src/components/index.ts index b4b29ce..67b8ac0 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,6 +1,6 @@ +import prettier from "prettier"; import comment from "../comment"; import type { File, HookFile } from "../types"; -import prettier from "prettier"; import { parseNameFormats } from "../utils"; const mapHookFileToComponent = async (hookFile: HookFile): Promise => { @@ -39,6 +39,7 @@ const mapHookFileToComponent = async (hookFile: HookFile): Promise => { > {JSON.stringify(${camelCasePlural}, null, 2)} +
) }; @@ -54,10 +55,150 @@ const mapHookFileToComponent = async (hookFile: HookFile): Promise => { }; }; +const mapHookFileToDeleteComponent = async ( + hookFile: HookFile +): Promise => { + const { + file: { fileName }, + } = hookFile; + + const componentName = fileName.replace("use", ""); + const { pascalCase, pascalCasePlural } = parseNameFormats(componentName); + + const content = ` + ${comment} + + "use client"; + + import { FormEventHandler, MouseEventHandler, useState } from "react"; + import ${fileName} from "../hooks/${fileName}"; + + export default function Delete${componentName}() { + const { delete${pascalCase} } = use${pascalCasePlural}(); + const [message, setMessage] = useState() + + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); + const target = event.target as typeof event.target & { + id: { value: string }; + }; + delete${pascalCase}(target.id.value as any).then((count) => { + if (count === 1) { + setMessage("row deleted!"); + } else { + setMessage("failed to delete row!"); + } + }); + }; + + const handleClick: MouseEventHandler = () => { + setMessage(undefined); + } + + if (message) { + return
+ {message} + +
+ } + + return ( +
+
+
+ + +
+ +
+
+ ); + } + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Delete${componentName}.tsx`, + content: formattedContent, + }; +}; + export const parseComponentFiles = async ( hookFiles: HookFile[] ): Promise => { + // const { tables } = await fetchTables(); + const componentPromises = hookFiles.map(mapHookFileToComponent); - const files = await Promise.all(componentPromises); + const deleteComponentPromises = hookFiles + .filter((hookFile) => hookFile.entityType === "TABLE") + .map(mapHookFileToDeleteComponent); + const files = await Promise.all([ + ...componentPromises, + ...deleteComponentPromises, + ]); + return files; }; diff --git a/src/hooks/joinTable.ts b/src/hooks/joinTable.ts index aa893e0..5a63af6 100644 --- a/src/hooks/joinTable.ts +++ b/src/hooks/joinTable.ts @@ -79,7 +79,8 @@ const mapJoinTableToFile = async ( file, location: `${DIRECTORY}/hooks/${file.fileName}.ts`, type: "HOOK", - entity: "TABLE", + entityType: "JOIN_TABLE", + entityName: joinTableFormats.name, usage, }; }; diff --git a/src/hooks/table.ts b/src/hooks/table.ts index 4d6ec92..c5ba608 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -77,20 +77,22 @@ const mapTableToFile = async (table: TableResponse): Promise => { } }; - const delete${pascalCase} = async (id: ${pascalCase}["id"]) => { + const delete${pascalCase} = async (id: ${pascalCase}["id"]): Promise => { try { - const { error } = await supabase + const { error, count } = await supabase .from("${tableName}") - .delete() + .delete({ count: "exact" }) .eq("id", id); if (error) { throw error; } const filtered = ${camelCasePlural}.filter((${camelCase}) => ${camelCase}.id !== id); set${pascalCasePlural}(filtered); + return count } catch (error) { console.error("Error deleting", error); } + return 0 }; return { ${camelCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} }; @@ -113,7 +115,8 @@ const mapTableToFile = async (table: TableResponse): Promise => { file, location: `${DIRECTORY}/hooks/${file.fileName}.ts`, type: "HOOK", - entity: "TABLE", + entityType: "TABLE", + entityName: tableName, usage, }; }; diff --git a/src/hooks/view.ts b/src/hooks/view.ts index 5270077..0b5df39 100644 --- a/src/hooks/view.ts +++ b/src/hooks/view.ts @@ -108,7 +108,8 @@ const mapViewToFile = async (viewName: string): Promise => { file, location: `${DIRECTORY}/hooks/${file.fileName}.ts`, type: "HOOK", - entity: "VIEW", + entityType: "VIEW", + entityName: viewName, usage, }; }; diff --git a/src/metadata.ts b/src/metadata.ts index 960718c..f8005e9 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -10,7 +10,8 @@ export const parseMetadataFile = async ( name: hookFile.file.fileName, location: hookFile.location, type: hookFile.type, - entity: hookFile.entity, + entityType: hookFile.entityType, + entityName: hookFile.entityName, usage: hookFile.usage, }; }) diff --git a/src/types.ts b/src/types.ts index 3dd5e7d..3cfeb8d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,8 @@ export type HookFile = { file: File; location: string; type: "HOOK"; - entity: "TABLE" | "VIEW"; + entityType: "TABLE" | "JOIN_TABLE" | "VIEW"; + entityName: string; usage: string; }; From 759416fb44b7bebe8447a4e925e2353be62b136b Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Thu, 24 Aug 2023 12:48:20 +0100 Subject: [PATCH 2/7] refactor get component --- src/components/delete.ts | 133 +++++++++++++++++++++++++ src/components/get.ts | 84 ++++++++++++++++ src/components/index.ts | 205 +++++++-------------------------------- src/hooks/joinTable.ts | 2 +- src/hooks/table.ts | 6 +- 5 files changed, 257 insertions(+), 173 deletions(-) create mode 100644 src/components/delete.ts create mode 100644 src/components/get.ts diff --git a/src/components/delete.ts b/src/components/delete.ts new file mode 100644 index 0000000..74d0699 --- /dev/null +++ b/src/components/delete.ts @@ -0,0 +1,133 @@ +import prettier from "prettier"; +import comment from "../comment"; +import type { File, HookFile } from "../types"; + +export const mapHookFileToDeleteComponent = async ( + hookFile: HookFile +): Promise => { + const { + file: { fileName }, + } = hookFile; + + const componentName = `${fileName.replace("use", "")}`; + + const content = ` + ${comment} + + "use client"; + + import { FormEventHandler, MouseEventHandler, useState } from "react"; + + export default function Delete${componentName}({ onDelete, onFetch }: { onDelete: (id: number) => Promise, onFetch: () => Promise }) { + const [message, setMessage] = useState(); + + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); + const target = event.target as typeof event.target & { + id: { value: string }; + }; + onDelete(target.id.value as any).then((count) => { + if (count === 1) { + setMessage("row deleted!"); + onFetch(); + } else { + setMessage("failed to delete row!"); + } + }); + }; + + const handleClick: MouseEventHandler = () => { + setMessage(undefined); + } + + if (message) { + return
+ {message} + +
+ } + + return ( +
+
+
+ + +
+ +
+
+ ); + } + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Delete${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/components/get.ts b/src/components/get.ts new file mode 100644 index 0000000..059f285 --- /dev/null +++ b/src/components/get.ts @@ -0,0 +1,84 @@ +import prettier from "prettier"; +import comment from "../comment"; +import type { File, HookFile } from "../types"; +import { parseNameFormats } from "../utils"; + +export const mapHookFileToGetComponent = async ( + hookFile: HookFile +): Promise => { + const { + file: { fileName }, + } = hookFile; + + const componentName = `${fileName.replace("use", "")}`; + const { camelCasePlural } = parseNameFormats(componentName); + + const content = ` + ${comment} + + import type { Row } from "../hooks/${fileName}"; + + export default function Get${componentName}({ ${camelCasePlural}, onFetch }: { ${camelCasePlural}: Row[], onFetch: () => Promise }) { + return ( +
+ ${camelCasePlural} +
+              {JSON.stringify(${camelCasePlural}, null, 2)}
+            
+ +
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Get${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/components/index.ts b/src/components/index.ts index 67b8ac0..005d624 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,60 +2,10 @@ import prettier from "prettier"; import comment from "../comment"; import type { File, HookFile } from "../types"; import { parseNameFormats } from "../utils"; +import { mapHookFileToDeleteComponent } from "./delete"; +import { mapHookFileToGetComponent } from "./get"; -const mapHookFileToComponent = async (hookFile: HookFile): Promise => { - const { - file: { fileName }, - } = hookFile; - - const componentName = fileName.replace("use", ""); - const { camelCasePlural } = parseNameFormats(componentName); - - const content = ` - ${comment} - - "use client"; - - import ${fileName} from "../hooks/${fileName}"; - - export default function ${componentName}() { - const { ${camelCasePlural} } = ${fileName}(); - - return ( -
- ${camelCasePlural} -
-            {JSON.stringify(${camelCasePlural}, null, 2)}
-          
-
-
- ) - }; - `; - - const formattedContent = await prettier.format(content, { - parser: "typescript", - }); - - return { - fileName: `${componentName}.tsx`, - content: formattedContent, - }; -}; - -const mapHookFileToDeleteComponent = async ( +export const mapHookFileToComponent = async ( hookFile: HookFile ): Promise => { const { @@ -63,125 +13,38 @@ const mapHookFileToDeleteComponent = async ( } = hookFile; const componentName = fileName.replace("use", ""); - const { pascalCase, pascalCasePlural } = parseNameFormats(componentName); + const getComponentName = `Get${componentName}`; + const deleteComponentName = `Delete${componentName}`; + const { camelCasePlural, pascalCase, pascalCasePlural } = + parseNameFormats(componentName); const content = ` - ${comment} - - "use client"; - - import { FormEventHandler, MouseEventHandler, useState } from "react"; - import ${fileName} from "../hooks/${fileName}"; - - export default function Delete${componentName}() { - const { delete${pascalCase} } = use${pascalCasePlural}(); - const [message, setMessage] = useState() - - const handleSubmit: FormEventHandler = (event) => { - event.preventDefault(); - const target = event.target as typeof event.target & { - id: { value: string }; - }; - delete${pascalCase}(target.id.value as any).then((count) => { - if (count === 1) { - setMessage("row deleted!"); - } else { - setMessage("failed to delete row!"); - } - }); + ${comment} + + "use client"; + + import ${fileName} from "../hooks/${fileName}"; + import ${getComponentName} from "./${getComponentName}"; + import ${deleteComponentName} from "./${deleteComponentName}"; + + export default function ${componentName}() { + const { ${camelCasePlural}, fetch${pascalCasePlural}, delete${pascalCase} } = ${fileName}(); + + return ( +
+ <${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> + <${deleteComponentName} onDelete={delete${pascalCase}} onFetch={fetch${pascalCasePlural}} /> +
+ ) }; - - const handleClick: MouseEventHandler = () => { - setMessage(undefined); - } - - if (message) { - return
- {message} - -
- } - - return ( -
-
-
- - -
- -
-
- ); - } - `; + `; const formattedContent = await prettier.format(content, { parser: "typescript", }); return { - fileName: `Delete${componentName}.tsx`, + fileName: `${componentName}.tsx`, content: formattedContent, }; }; @@ -189,16 +52,20 @@ const mapHookFileToDeleteComponent = async ( export const parseComponentFiles = async ( hookFiles: HookFile[] ): Promise => { - // const { tables } = await fetchTables(); + const tableHookFiles = hookFiles.filter( + (hookFile) => hookFile.entityType === "TABLE" + ); + + const componentPromises = tableHookFiles.map(mapHookFileToComponent); + const getComponentPromises = tableHookFiles.map(mapHookFileToGetComponent); + const deleteComponentPromises = tableHookFiles.map( + mapHookFileToDeleteComponent + ); - const componentPromises = hookFiles.map(mapHookFileToComponent); - const deleteComponentPromises = hookFiles - .filter((hookFile) => hookFile.entityType === "TABLE") - .map(mapHookFileToDeleteComponent); const files = await Promise.all([ ...componentPromises, + ...getComponentPromises, ...deleteComponentPromises, ]); - return files; }; diff --git a/src/hooks/joinTable.ts b/src/hooks/joinTable.ts index 5a63af6..b709679 100644 --- a/src/hooks/joinTable.ts +++ b/src/hooks/joinTable.ts @@ -73,7 +73,7 @@ const mapJoinTableToFile = async ( fileName: `use${joinTableFormats.pascalCasePlural}`, content: formattedContent, }; - const usage = `const { ${tableOneFetchFunctionName} } = use${joinTableFormats.pascalCasePlural}();`; + const usage = `const { ${tableOneFetchFunctionName}, ${tableTwoFetchFunctionName} } = use${joinTableFormats.pascalCasePlural}();`; return { file, diff --git a/src/hooks/table.ts b/src/hooks/table.ts index c5ba608..97c0e9a 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -17,12 +17,12 @@ const mapTableToFile = async (table: TableResponse): Promise => { import { Database } from "../types"; type Table = Database["public"]["Tables"]["${tableName}"] - type ${pascalCase} = Table["Row"]; + export type Row = Table["Row"]; type Insert${pascalCase} = Table["Insert"]; type Update${pascalCase} = Table["Update"]; const use${pascalCasePlural} = () => { - const [${camelCasePlural}, set${pascalCasePlural}] = useState<${pascalCase}[]>([]); + const [${camelCasePlural}, set${pascalCasePlural}] = useState([]); useEffect(() => { fetch${pascalCasePlural}(); @@ -95,7 +95,7 @@ const mapTableToFile = async (table: TableResponse): Promise => { return 0 }; - return { ${camelCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} }; + return { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} }; }; export default use${pascalCasePlural}; From 5b6114cdc425212e9c73907fe891ab812df81b0d Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Thu, 24 Aug 2023 18:09:39 +0100 Subject: [PATCH 3/7] generate post component --- src/components/create.ts | 171 +++++++++++++++++++++++++++++++++++++++ src/components/index.ts | 13 ++- src/hooks/joinTable.ts | 2 +- src/hooks/table.ts | 11 +-- 4 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 src/components/create.ts diff --git a/src/components/create.ts b/src/components/create.ts new file mode 100644 index 0000000..a312a20 --- /dev/null +++ b/src/components/create.ts @@ -0,0 +1,171 @@ +import prettier from "prettier"; +import comment from "../comment"; +import type { ColumnResponse, TablesResponse } from "../pgMeta/fetchTables"; +import type { File, HookFile } from "../types"; +import { parseNameFormats } from "../utils"; + +const mapColumns = ( + columns?: ColumnResponse[] +): { fields: string; inputs: string } => { + if (!columns) { + return { fields: "", inputs: "" }; + } + + const filteredColumns = columns.filter((column) => !column.isIdentity); + const fields = filteredColumns.map((column) => `"${column.name}"`).join(","); + const inputs = filteredColumns + .map((column, index) => { + return ` +
+ + 0 ? 'marginTop: "10px",' : ""} + background: "#000", + color: "#fff", + border: "1px solid #34383A", + marginLeft: "10px", + flex: "1", + borderRadius: "0.375rem", + padding: "4px 16px", + }} + /> +
+ `; + }) + .join(" "); + + return { fields, inputs }; +}; + +export const mapHookFileToCreateComponent = async ( + hookFile: HookFile, + tables: TablesResponse +): Promise => { + const { + entityName, + file: { fileName }, + } = hookFile; + + const table = tables.find((table) => table.name === entityName); + const componentName = `${fileName.replace("use", "")}`; + const { pascalCase } = parseNameFormats(componentName); + + const { fields, inputs } = mapColumns(table?.columns); + + const content = ` + ${comment} + + "use client"; + + import { FormEventHandler, MouseEventHandler, useState } from "react"; + import type { Row, Insert${pascalCase} } from "../hooks/${fileName}"; + + const fields: Array = [${fields}] + + export default function Create${componentName}({ onCreate, onFetch }: { onCreate: (newRow: Insert${pascalCase}) => Promise, onFetch: () => Promise }) { + const [message, setMessage] = useState(); + + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); + const target = event.target as typeof event.target & Insert${pascalCase}; + const newRow = fields + .map((field) => ({ field, value: (target[field] as any)?.value })) + .reduce((newRow, { field,value }) => { + if (value.trim() !== "") { + newRow[field] = value; + } + return newRow; + }, {} as Record); + onCreate(newRow).then((task) => { + if (task) { + setMessage("row with id " + task.id + " created!"); + onFetch(); + } else { + setMessage("failed to create row!"); + } + }); + }; + + const handleClick: MouseEventHandler = () => { + setMessage(undefined); + } + + if (message) { + return
+ {message} + +
+ } + + return ( +
+
+ ${inputs} + +
+
+ ); + } + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Create${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/components/index.ts b/src/components/index.ts index 005d624..ad18bca 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,7 +1,9 @@ import prettier from "prettier"; import comment from "../comment"; +import { fetchTables } from "../pgMeta/fetchTables"; import type { File, HookFile } from "../types"; import { parseNameFormats } from "../utils"; +import { mapHookFileToCreateComponent } from "./create"; import { mapHookFileToDeleteComponent } from "./delete"; import { mapHookFileToGetComponent } from "./get"; @@ -14,6 +16,7 @@ export const mapHookFileToComponent = async ( const componentName = fileName.replace("use", ""); const getComponentName = `Get${componentName}`; + const createComponentName = `Create${componentName}`; const deleteComponentName = `Delete${componentName}`; const { camelCasePlural, pascalCase, pascalCasePlural } = parseNameFormats(componentName); @@ -25,14 +28,16 @@ export const mapHookFileToComponent = async ( import ${fileName} from "../hooks/${fileName}"; import ${getComponentName} from "./${getComponentName}"; + import ${createComponentName} from "./${createComponentName}"; import ${deleteComponentName} from "./${deleteComponentName}"; export default function ${componentName}() { - const { ${camelCasePlural}, fetch${pascalCasePlural}, delete${pascalCase} } = ${fileName}(); + const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, delete${pascalCase} } = ${fileName}(); return (
<${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> + <${createComponentName} onCreate={create${pascalCase}} onFetch={fetch${pascalCasePlural}} /> <${deleteComponentName} onDelete={delete${pascalCase}} onFetch={fetch${pascalCasePlural}} />
) @@ -52,18 +57,24 @@ export const mapHookFileToComponent = async ( export const parseComponentFiles = async ( hookFiles: HookFile[] ): Promise => { + const { tables } = await fetchTables(); + const tableHookFiles = hookFiles.filter( (hookFile) => hookFile.entityType === "TABLE" ); const componentPromises = tableHookFiles.map(mapHookFileToComponent); const getComponentPromises = tableHookFiles.map(mapHookFileToGetComponent); + const createComponentPromises = tableHookFiles.map((tableHookFile) => + mapHookFileToCreateComponent(tableHookFile, tables) + ); const deleteComponentPromises = tableHookFiles.map( mapHookFileToDeleteComponent ); const files = await Promise.all([ ...componentPromises, + ...createComponentPromises, ...getComponentPromises, ...deleteComponentPromises, ]); diff --git a/src/hooks/joinTable.ts b/src/hooks/joinTable.ts index b709679..625033e 100644 --- a/src/hooks/joinTable.ts +++ b/src/hooks/joinTable.ts @@ -1,6 +1,6 @@ import prettier from "prettier"; import comment from "../comment"; -import { TableResponse, TablesResponse } from "../pgMeta/fetchTables"; +import type { TableResponse, TablesResponse } from "../pgMeta/fetchTables"; import type { File, HookFile } from "../types"; import { DIRECTORY, parseNameFormats } from "../utils"; diff --git a/src/hooks/table.ts b/src/hooks/table.ts index 97c0e9a..9b3bca4 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -18,8 +18,8 @@ const mapTableToFile = async (table: TableResponse): Promise => { type Table = Database["public"]["Tables"]["${tableName}"] export type Row = Table["Row"]; - type Insert${pascalCase} = Table["Insert"]; - type Update${pascalCase} = Table["Update"]; + export type Insert${pascalCase} = Table["Insert"]; + export type Update${pascalCase} = Table["Update"]; const use${pascalCasePlural} = () => { const [${camelCasePlural}, set${pascalCasePlural}] = useState([]); @@ -52,12 +52,13 @@ const mapTableToFile = async (table: TableResponse): Promise => { throw error; } set${pascalCasePlural}([...${camelCasePlural}, data[0]]); + return data[0] } catch (error) { console.error("Error creating", error); } }; - const update${pascalCase} = async (id: ${pascalCase}["id"], updatedData: Update${pascalCase}) => { + const update${pascalCase} = async (id: Row["id"], updatedData: Update${pascalCase}) => { try { const { data, error } = await supabase .from("${tableName}") @@ -77,7 +78,7 @@ const mapTableToFile = async (table: TableResponse): Promise => { } }; - const delete${pascalCase} = async (id: ${pascalCase}["id"]): Promise => { + const delete${pascalCase} = async (id: Row["id"]): Promise => { try { const { error, count } = await supabase .from("${tableName}") @@ -109,7 +110,7 @@ const mapTableToFile = async (table: TableResponse): Promise => { fileName: `use${pascalCasePlural}`, content: formattedContent, }; - const usage = `const { ${camelCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} } = use${pascalCasePlural}();`; + const usage = `const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} } = use${pascalCasePlural}();`; return { file, From bc95f5c7bbd18c2d8e33ee4975575c56e9b2de9d Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Sat, 26 Aug 2023 17:02:17 +0100 Subject: [PATCH 4/7] components for views --- index.ts | 25 +++++--- src/components/index.ts | 82 +++----------------------- src/components/{ => tables}/create.ts | 47 ++++++++++----- src/components/{ => tables}/delete.ts | 13 ++++- src/components/{ => tables}/get.ts | 9 ++- src/components/tables/index.ts | 81 ++++++++++++++++++++++++++ src/components/views/get.ts | 83 +++++++++++++++++++++++++++ src/components/views/index.ts | 62 ++++++++++++++++++++ src/hooks/table.ts | 43 ++++++-------- src/hooks/view.ts | 29 +++++----- 10 files changed, 331 insertions(+), 143 deletions(-) rename src/components/{ => tables}/create.ts (81%) rename src/components/{ => tables}/delete.ts (93%) rename src/components/{ => tables}/get.ts (90%) create mode 100644 src/components/tables/index.ts create mode 100644 src/components/views/get.ts create mode 100644 src/components/views/index.ts diff --git a/index.ts b/index.ts index 0977c59..26a57b1 100644 --- a/index.ts +++ b/index.ts @@ -13,7 +13,7 @@ const writeFiles = async ( supabaseFile: File, hookFiles: HookFile[], metadataFile: File, - componentFiles: File[] + componentFiles: { tableComponents: File[]; viewComponents: File[] } ) => { await writeFile( `${DIRECTORY}/${supabaseFile.fileName}`, @@ -29,14 +29,24 @@ const writeFiles = async ( `${DIRECTORY}/${metadataFile.fileName}`, metadataFile.content ); - await Promise.all( - componentFiles.map((hookFile) => { + + const tableComponentPromises = componentFiles.tableComponents.map( + (componentFile) => { return writeFile( - `${DIRECTORY}/components/${hookFile.fileName}`, - hookFile.content + `${DIRECTORY}/components/tables/${componentFile.fileName}`, + componentFile.content ); - }) + } + ); + const viewComponentPromises = componentFiles.viewComponents.map( + (componentFile) => { + return writeFile( + `${DIRECTORY}/components/views/${componentFile.fileName}`, + componentFile.content + ); + } ); + await Promise.all([...tableComponentPromises, ...viewComponentPromises]); }; const run = async () => { @@ -45,7 +55,8 @@ const run = async () => { await remove(DIRECTORY); await ensureDir(`${DIRECTORY}/hooks`); - await ensureDir(`${DIRECTORY}/components`); + await ensureDir(`${DIRECTORY}/components/tables`); + await ensureDir(`${DIRECTORY}/components/views`); await writeFile(`${DIRECTORY}/${types.fileName}`, types.content); const supabaseFile = await parseSupabaseFile(); diff --git a/src/components/index.ts b/src/components/index.ts index ad18bca..ec10244 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,82 +1,18 @@ -import prettier from "prettier"; -import comment from "../comment"; import { fetchTables } from "../pgMeta/fetchTables"; import type { File, HookFile } from "../types"; -import { parseNameFormats } from "../utils"; -import { mapHookFileToCreateComponent } from "./create"; -import { mapHookFileToDeleteComponent } from "./delete"; -import { mapHookFileToGetComponent } from "./get"; - -export const mapHookFileToComponent = async ( - hookFile: HookFile -): Promise => { - const { - file: { fileName }, - } = hookFile; - - const componentName = fileName.replace("use", ""); - const getComponentName = `Get${componentName}`; - const createComponentName = `Create${componentName}`; - const deleteComponentName = `Delete${componentName}`; - const { camelCasePlural, pascalCase, pascalCasePlural } = - parseNameFormats(componentName); - - const content = ` - ${comment} - - "use client"; - - import ${fileName} from "../hooks/${fileName}"; - import ${getComponentName} from "./${getComponentName}"; - import ${createComponentName} from "./${createComponentName}"; - import ${deleteComponentName} from "./${deleteComponentName}"; - - export default function ${componentName}() { - const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, delete${pascalCase} } = ${fileName}(); - - return ( -
- <${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> - <${createComponentName} onCreate={create${pascalCase}} onFetch={fetch${pascalCasePlural}} /> - <${deleteComponentName} onDelete={delete${pascalCase}} onFetch={fetch${pascalCasePlural}} /> -
- ) - }; - `; - - const formattedContent = await prettier.format(content, { - parser: "typescript", - }); - - return { - fileName: `${componentName}.tsx`, - content: formattedContent, - }; -}; +import { parseComponentFilesForTables } from "./tables"; +import { parseComponentFilesForViews } from "./views"; export const parseComponentFiles = async ( hookFiles: HookFile[] -): Promise => { +): Promise<{ tableComponents: File[]; viewComponents: File[] }> => { const { tables } = await fetchTables(); - const tableHookFiles = hookFiles.filter( - (hookFile) => hookFile.entityType === "TABLE" - ); - - const componentPromises = tableHookFiles.map(mapHookFileToComponent); - const getComponentPromises = tableHookFiles.map(mapHookFileToGetComponent); - const createComponentPromises = tableHookFiles.map((tableHookFile) => - mapHookFileToCreateComponent(tableHookFile, tables) - ); - const deleteComponentPromises = tableHookFiles.map( - mapHookFileToDeleteComponent - ); + const tableComponents = await parseComponentFilesForTables(hookFiles, tables); + const viewComponents = await parseComponentFilesForViews(hookFiles); - const files = await Promise.all([ - ...componentPromises, - ...createComponentPromises, - ...getComponentPromises, - ...deleteComponentPromises, - ]); - return files; + return { + tableComponents, + viewComponents, + }; }; diff --git a/src/components/create.ts b/src/components/tables/create.ts similarity index 81% rename from src/components/create.ts rename to src/components/tables/create.ts index a312a20..f4adf28 100644 --- a/src/components/create.ts +++ b/src/components/tables/create.ts @@ -1,8 +1,8 @@ import prettier from "prettier"; -import comment from "../comment"; -import type { ColumnResponse, TablesResponse } from "../pgMeta/fetchTables"; -import type { File, HookFile } from "../types"; -import { parseNameFormats } from "../utils"; +import comment from "../../comment"; +import type { ColumnResponse, TablesResponse } from "../../pgMeta/fetchTables"; +import type { File, HookFile } from "../../types"; +import { parseNameFormats } from "../../utils"; const mapColumns = ( columns?: ColumnResponse[] @@ -15,9 +15,20 @@ const mapColumns = ( const fields = filteredColumns.map((column) => `"${column.name}"`).join(","); const inputs = filteredColumns .map((column, index) => { + const label = + !column.isNullable && !column.defaultValue + ? `${column.name}*` + : column.name; return `
- + = [${fields}] @@ -79,14 +90,22 @@ export const mapHookFileToCreateComponent = async ( } return newRow; }, {} as Record); - onCreate(newRow).then((task) => { - if (task) { - setMessage("row with id " + task.id + " created!"); - onFetch(); - } else { - setMessage("failed to create row!"); - } - }); + onCreate(newRow) + .then((task) => { + if (task) { + setMessage("row with id " + task.id + " created!"); + onFetch(); + } else { + setMessage("failed to create row!"); + } + }) + .catch((error) => { + if (error.message) { + setMessage(error.message); + } else { + setMessage("failed to create row!"); + } + }); }; const handleClick: MouseEventHandler = () => { diff --git a/src/components/delete.ts b/src/components/tables/delete.ts similarity index 93% rename from src/components/delete.ts rename to src/components/tables/delete.ts index 74d0699..0449d6d 100644 --- a/src/components/delete.ts +++ b/src/components/tables/delete.ts @@ -1,6 +1,6 @@ import prettier from "prettier"; -import comment from "../comment"; -import type { File, HookFile } from "../types"; +import comment from "../../comment"; +import type { File, HookFile } from "../../types"; export const mapHookFileToDeleteComponent = async ( hookFile: HookFile @@ -70,7 +70,14 @@ export const mapHookFileToDeleteComponent = async ( >
- + Promise }) { return ( @@ -25,7 +25,6 @@ export const mapHookFileToGetComponent = async ( paddingTop: "20px", }} > - ${camelCasePlural}
 => {
+  const {
+    file: { fileName },
+  } = hookFile;
+
+  const componentName = fileName.replace("use", "");
+  const getComponentName = `Get${componentName}`;
+  const createComponentName = `Create${componentName}`;
+  const deleteComponentName = `Delete${componentName}`;
+  const { camelCasePlural, pascalCase, pascalCasePlural } =
+    parseNameFormats(componentName);
+
+  const content = `
+      ${comment}
+  
+      "use client";
+  
+      import ${fileName} from "../../hooks/${fileName}";
+      import ${getComponentName} from "./${getComponentName}";
+      import ${createComponentName} from "./${createComponentName}";
+      import ${deleteComponentName} from "./${deleteComponentName}";
+  
+      export default function ${componentName}() {
+        const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, delete${pascalCase} } = ${fileName}();
+  
+        return (
+          
+ <${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> + <${createComponentName} onCreate={create${pascalCase}} onFetch={fetch${pascalCasePlural}} /> + <${deleteComponentName} onDelete={delete${pascalCase}} onFetch={fetch${pascalCasePlural}} /> +
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `${componentName}.tsx`, + content: formattedContent, + }; +}; + +export const parseComponentFilesForTables = async ( + hookFiles: HookFile[], + tables: TablesResponse +): Promise => { + const tableHookFiles = hookFiles.filter( + (hookFile) => hookFile.entityType === "TABLE" + ); + + const componentPromises = tableHookFiles.map(mapHookFileToComponent); + const getComponentPromises = tableHookFiles.map(mapHookFileToGetComponent); + const createComponentPromises = tableHookFiles.map((tableHookFile) => + mapHookFileToCreateComponent(tableHookFile, tables) + ); + const deleteComponentPromises = tableHookFiles.map( + mapHookFileToDeleteComponent + ); + + const files = await Promise.all([ + ...componentPromises, + ...createComponentPromises, + ...getComponentPromises, + ...deleteComponentPromises, + ]); + return files; +}; diff --git a/src/components/views/get.ts b/src/components/views/get.ts new file mode 100644 index 0000000..19a40d9 --- /dev/null +++ b/src/components/views/get.ts @@ -0,0 +1,83 @@ +import prettier from "prettier"; +import comment from "../../comment"; +import type { File, HookFile } from "../../types"; +import { parseNameFormats } from "../../utils"; + +export const mapHookFileToGetComponent = async ( + hookFile: HookFile +): Promise => { + const { + file: { fileName }, + } = hookFile; + + const componentName = `${fileName.replace("use", "")}`; + const { camelCasePlural } = parseNameFormats(componentName); + + const content = ` + ${comment} + + import type { Row } from "../../hooks/${fileName}"; + + export default function Get${componentName}({ ${camelCasePlural}, onFetch }: { ${camelCasePlural}: Row[], onFetch: () => Promise }) { + return ( +
+
+              {JSON.stringify(${camelCasePlural}, null, 2)}
+            
+ +
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Get${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/components/views/index.ts b/src/components/views/index.ts new file mode 100644 index 0000000..e842818 --- /dev/null +++ b/src/components/views/index.ts @@ -0,0 +1,62 @@ +import prettier from "prettier"; +import comment from "../../comment"; +import type { File, HookFile } from "../../types"; +import { parseNameFormats } from "../../utils"; +import { mapHookFileToGetComponent } from "./get"; + +export const mapHookFileToComponent = async ( + hookFile: HookFile +): Promise => { + const { + file: { fileName }, + } = hookFile; + + const componentName = fileName.replace("use", ""); + const getComponentName = `Get${componentName}`; + const { camelCasePlural, pascalCasePlural } = parseNameFormats(componentName); + + const content = ` + ${comment} + + "use client"; + + import ${fileName} from "../../hooks/${fileName}"; + import ${getComponentName} from "./${getComponentName}"; + + export default function ${componentName}() { + const { ${camelCasePlural}, fetch${pascalCasePlural} } = ${fileName}(); + + return ( +
+ <${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> +
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `${componentName}.tsx`, + content: formattedContent, + }; +}; + +export const parseComponentFilesForViews = async ( + hookFiles: HookFile[] +): Promise => { + const viewHookFiles = hookFiles.filter( + (hookFile) => hookFile.entityType === "VIEW" + ); + + const componentPromises = viewHookFiles.map(mapHookFileToComponent); + const getComponentPromises = viewHookFiles.map(mapHookFileToGetComponent); + + const files = await Promise.all([ + ...componentPromises, + ...getComponentPromises, + ]); + return files; +}; diff --git a/src/hooks/table.ts b/src/hooks/table.ts index 9b3bca4..79fdf68 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -43,19 +43,15 @@ const mapTableToFile = async (table: TableResponse): Promise => { }; const create${pascalCase} = async (newData: Insert${pascalCase}) => { - try { - const { data, error } = await supabase - .from("${tableName}") - .insert([newData]) - .select(); - if (error) { - throw error; - } - set${pascalCasePlural}([...${camelCasePlural}, data[0]]); - return data[0] - } catch (error) { - console.error("Error creating", error); + const { data, error } = await supabase + .from("${tableName}") + .insert([newData]) + .select(); + if (error) { + throw error; } + set${pascalCasePlural}([...${camelCasePlural}, data[0]]); + return data[0] }; const update${pascalCase} = async (id: Row["id"], updatedData: Update${pascalCase}) => { @@ -79,21 +75,16 @@ const mapTableToFile = async (table: TableResponse): Promise => { }; const delete${pascalCase} = async (id: Row["id"]): Promise => { - try { - const { error, count } = await supabase - .from("${tableName}") - .delete({ count: "exact" }) - .eq("id", id); - if (error) { - throw error; - } - const filtered = ${camelCasePlural}.filter((${camelCase}) => ${camelCase}.id !== id); - set${pascalCasePlural}(filtered); - return count - } catch (error) { - console.error("Error deleting", error); + const { error, count } = await supabase + .from("${tableName}") + .delete({ count: "exact" }) + .eq("id", id); + if (error) { + throw error; } - return 0 + const filtered = ${camelCasePlural}.filter((${camelCase}) => ${camelCase}.id !== id); + set${pascalCasePlural}(filtered); + return count }; return { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} }; diff --git a/src/hooks/view.ts b/src/hooks/view.ts index 0b5df39..848a24f 100644 --- a/src/hooks/view.ts +++ b/src/hooks/view.ts @@ -54,8 +54,7 @@ const parseViewNames = async (): Promise => { }; const mapViewToFile = async (viewName: string): Promise => { - const { pascalCase, pascalCasePlural, camelCasePlural } = - parseNameFormats(viewName); + const { pascalCasePlural, camelCasePlural } = parseNameFormats(viewName); const content = ` ${comment} @@ -65,30 +64,30 @@ const mapViewToFile = async (viewName: string): Promise => { import { Database } from "../types"; type View = Database["public"]["Views"]["${viewName}"] - type ${pascalCase} = View["Row"]; + export type Row = View["Row"]; const use${pascalCasePlural} = () => { - const [${camelCasePlural}, set${pascalCasePlural}] = useState<${pascalCase}[]>([]); + const [${camelCasePlural}, set${pascalCasePlural}] = useState([]); useEffect(() => { fetch${pascalCasePlural}(); }, []); const fetch${pascalCasePlural} = async() => { - try { - const { data, error } = await supabase - .from("${viewName}") - .select(); - if (error) { - throw error; - } - set${pascalCasePlural}(data || []); - } catch (error) { - console.error("Error fetching", error); + try { + const { data, error } = await supabase + .from("${viewName}") + .select(); + if (error) { + throw error; } + set${pascalCasePlural}(data || []); + } catch (error) { + console.error("Error fetching", error); + } }; - return { ${camelCasePlural} }; + return { ${camelCasePlural}, fetch${pascalCasePlural} }; }; export default use${pascalCasePlural}; From 11e414a54d2822a9018de41be05553770f9676f7 Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Sat, 26 Aug 2023 20:25:04 +0100 Subject: [PATCH 5/7] components for join tables --- index.ts | 21 +++- src/components/index.ts | 14 ++- src/components/joinTables/get.ts | 164 +++++++++++++++++++++++++++++ src/components/joinTables/index.ts | 72 +++++++++++++ src/components/joinTables/utils.ts | 18 ++++ src/components/tables/delete.ts | 2 +- src/hooks/joinTable.ts | 44 ++++---- 7 files changed, 304 insertions(+), 31 deletions(-) create mode 100644 src/components/joinTables/get.ts create mode 100644 src/components/joinTables/index.ts create mode 100644 src/components/joinTables/utils.ts diff --git a/index.ts b/index.ts index 26a57b1..025d5fe 100644 --- a/index.ts +++ b/index.ts @@ -13,7 +13,11 @@ const writeFiles = async ( supabaseFile: File, hookFiles: HookFile[], metadataFile: File, - componentFiles: { tableComponents: File[]; viewComponents: File[] } + componentFiles: { + tableComponents: File[]; + joinTableComponents: File[]; + viewComponents: File[]; + } ) => { await writeFile( `${DIRECTORY}/${supabaseFile.fileName}`, @@ -38,6 +42,14 @@ const writeFiles = async ( ); } ); + const joinTableComponentPromises = componentFiles.joinTableComponents.map( + (componentFile) => { + return writeFile( + `${DIRECTORY}/components/joinTables/${componentFile.fileName}`, + componentFile.content + ); + } + ); const viewComponentPromises = componentFiles.viewComponents.map( (componentFile) => { return writeFile( @@ -46,7 +58,11 @@ const writeFiles = async ( ); } ); - await Promise.all([...tableComponentPromises, ...viewComponentPromises]); + await Promise.all([ + ...tableComponentPromises, + ...joinTableComponentPromises, + ...viewComponentPromises, + ]); }; const run = async () => { @@ -56,6 +72,7 @@ const run = async () => { await remove(DIRECTORY); await ensureDir(`${DIRECTORY}/hooks`); await ensureDir(`${DIRECTORY}/components/tables`); + await ensureDir(`${DIRECTORY}/components/joinTables`); await ensureDir(`${DIRECTORY}/components/views`); await writeFile(`${DIRECTORY}/${types.fileName}`, types.content); diff --git a/src/components/index.ts b/src/components/index.ts index ec10244..7f9f803 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,18 +1,28 @@ import { fetchTables } from "../pgMeta/fetchTables"; import type { File, HookFile } from "../types"; import { parseComponentFilesForTables } from "./tables"; +import { parseComponentFilesForJoinTables } from "./joinTables"; import { parseComponentFilesForViews } from "./views"; export const parseComponentFiles = async ( hookFiles: HookFile[] -): Promise<{ tableComponents: File[]; viewComponents: File[] }> => { - const { tables } = await fetchTables(); +): Promise<{ + tableComponents: File[]; + joinTableComponents: File[]; + viewComponents: File[]; +}> => { + const { tables, joinTables } = await fetchTables(); const tableComponents = await parseComponentFilesForTables(hookFiles, tables); + const joinTableComponents = await parseComponentFilesForJoinTables( + hookFiles, + joinTables + ); const viewComponents = await parseComponentFilesForViews(hookFiles); return { tableComponents, + joinTableComponents, viewComponents, }; }; diff --git a/src/components/joinTables/get.ts b/src/components/joinTables/get.ts new file mode 100644 index 0000000..72cf4e4 --- /dev/null +++ b/src/components/joinTables/get.ts @@ -0,0 +1,164 @@ +import prettier from "prettier"; +import comment from "../../comment"; +import { TablesResponse } from "../../pgMeta/fetchTables"; +import type { File, HookFile } from "../../types"; +import { parseFetchFunctionNamesForJoinTable } from "./utils"; + +export const mapHookFileToGetComponent = async ( + hookFile: HookFile, + joinTables: TablesResponse +): Promise => { + const { + entityName, + file: { fileName }, + } = hookFile; + + const componentName = `${fileName.replace("use", "")}`; + + const { tableOneFetchFunctionName, tableTwoFetchFunctionName } = + parseFetchFunctionNamesForJoinTable(joinTables, entityName); + + const content = ` + ${comment} + + import { useState } from "react"; + import type { TableOneRow, TableTwoRow } from "../../hooks/${fileName}"; + + export default function Get${componentName}({ + ${tableOneFetchFunctionName}, + ${tableTwoFetchFunctionName} + }: + { + ${tableOneFetchFunctionName}: (id: TableOneRow["id"]) => Promise, + ${tableTwoFetchFunctionName}: (id: TableTwoRow["id"]) => Promise + }) { + const [id, setId] = useState(""); + const [data, setData] = useState(); + + return ( +
+
+              {JSON.stringify(data, null, 2)}
+            
+
+ + setId(event.target.value)} + /> +
+
+ + +
+
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Get${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/components/joinTables/index.ts b/src/components/joinTables/index.ts new file mode 100644 index 0000000..7a7ecfc --- /dev/null +++ b/src/components/joinTables/index.ts @@ -0,0 +1,72 @@ +import prettier from "prettier"; +import comment from "../../comment"; +import type { TablesResponse } from "../../pgMeta/fetchTables"; +import type { File, HookFile } from "../../types"; +import { mapHookFileToGetComponent } from "./get"; +import { parseFetchFunctionNamesForJoinTable } from "./utils"; + +export const mapHookFileToComponent = async ( + hookFile: HookFile, + joinTables: TablesResponse +): Promise => { + const { + entityName, + file: { fileName }, + } = hookFile; + + const componentName = fileName.replace("use", ""); + const getComponentName = `Get${componentName}`; + + const { tableOneFetchFunctionName, tableTwoFetchFunctionName } = + parseFetchFunctionNamesForJoinTable(joinTables, entityName); + + const content = ` + ${comment} + + "use client"; + + import ${fileName} from "../../hooks/${fileName}"; + import ${getComponentName} from "./${getComponentName}"; + + export default function ${componentName}() { + const { ${tableOneFetchFunctionName}, ${tableTwoFetchFunctionName} } = ${fileName}(); + + return ( +
+ <${getComponentName} ${tableOneFetchFunctionName}={${tableOneFetchFunctionName}} ${tableTwoFetchFunctionName}={${tableTwoFetchFunctionName}} /> +
+ ) + }; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `${componentName}.tsx`, + content: formattedContent, + }; +}; + +export const parseComponentFilesForJoinTables = async ( + hookFiles: HookFile[], + joinTables: TablesResponse +): Promise => { + const joinTableHookFiles = hookFiles.filter( + (hookFile) => hookFile.entityType === "JOIN_TABLE" + ); + + const componentPromises = joinTableHookFiles.map((joinTableHookFile) => + mapHookFileToComponent(joinTableHookFile, joinTables) + ); + const getComponentPromises = joinTableHookFiles.map((joinTableHookFile) => + mapHookFileToGetComponent(joinTableHookFile, joinTables) + ); + + const files = await Promise.all([ + ...componentPromises, + ...getComponentPromises, + ]); + return files; +}; diff --git a/src/components/joinTables/utils.ts b/src/components/joinTables/utils.ts new file mode 100644 index 0000000..4cd6eb7 --- /dev/null +++ b/src/components/joinTables/utils.ts @@ -0,0 +1,18 @@ +import type { TablesResponse } from "../../pgMeta/fetchTables"; +import { parseNameFormats } from "../../utils"; + +export const parseFetchFunctionNamesForJoinTable = ( + joinTables: TablesResponse, + entityName: string +) => { + const joinTable = joinTables.find((table) => table.name === entityName)!; + const tableOneFormats = parseNameFormats( + joinTable.relationships.at(0)!.targetTableName + ); + const tableTwoFormats = parseNameFormats( + joinTable.relationships.at(1)!.targetTableName + ); + const tableOneFetchFunctionName = `fetch${tableTwoFormats.pascalCasePlural}For${tableOneFormats.pascalCase}`; + const tableTwoFetchFunctionName = `fetch${tableOneFormats.pascalCasePlural}For${tableTwoFormats.pascalCase}`; + return { tableOneFetchFunctionName, tableTwoFetchFunctionName }; +}; diff --git a/src/components/tables/delete.ts b/src/components/tables/delete.ts index 0449d6d..1f8d095 100644 --- a/src/components/tables/delete.ts +++ b/src/components/tables/delete.ts @@ -76,7 +76,7 @@ export const mapHookFileToDeleteComponent = async ( flexBasis: "120px", }} > - ID: + ID { - const ${tableOneFetchFunctionName} = async(id: ${tableOneFormats.pascalCase}Row["id"]) => { - try { - const { data, error } = await supabase - .from("${tableOneFormats.name}") - .select("*, ${tableTwoFormats.name}(*)") - .eq("id", id); - if (error) { - throw error; - } - return data - } catch (error) { - console.error("Error fetching", error); + const ${tableOneFetchFunctionName} = async(id: TableOneRow["id"]) => { + const { data, error } = await supabase + .from("${tableOneFormats.name}") + .select("*, ${tableTwoFormats.name}(*)") + .eq("id", id); + if (error) { + throw error; } + return data.map((d) => d.${tableTwoFormats.name}).flat(); }; - const ${tableTwoFetchFunctionName} = async(id: ${tableTwoFormats.pascalCase}Row["id"]) => { - try { - const { data, error } = await supabase - .from("${tableTwoFormats.name}") - .select("*, ${tableOneFormats.name}(*)") - .eq("id", id); - if (error) { - throw error; - } - return data - } catch (error) { - console.error("Error fetching", error); + const ${tableTwoFetchFunctionName} = async(id: TableTwoRow["id"]) => { + const { data, error } = await supabase + .from("${tableTwoFormats.name}") + .select("*, ${tableOneFormats.name}(*)") + .eq("id", id); + if (error) { + throw error; } + return data.map((d) => d.${tableOneFormats.name}).flat(); }; return { ${tableOneFetchFunctionName}, ${tableTwoFetchFunctionName} }; From 34bc9316ae92db1eb141524cd4d41f85856492cc Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Tue, 29 Aug 2023 17:33:16 +0100 Subject: [PATCH 6/7] show data type for input fields --- package.json | 2 +- src/components/joinTables/get.ts | 4 ++-- src/components/tables/create.ts | 11 ++++++++++- src/components/tables/delete.ts | 30 +++++++++++++++++++++++++++--- src/components/tables/index.ts | 4 ++-- src/pgMeta/fetchTables.ts | 1 + 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 21cbc6a..e0dd213 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@backengine/codegen", - "version": "1.0.16", + "version": "2.0.0", "description": "Generate code for Backengine projects.", "bin": "build/index.js", "files": [ diff --git a/src/components/joinTables/get.ts b/src/components/joinTables/get.ts index 72cf4e4..ce62884 100644 --- a/src/components/joinTables/get.ts +++ b/src/components/joinTables/get.ts @@ -91,7 +91,7 @@ export const mapHookFileToGetComponent = async ( background: "#fff", color: "#000", padding: "8px 10px", - width: "200px", + minWidth: "200px", borderRadius: "0.375rem", display: "flex", alignItems: "center", @@ -122,7 +122,7 @@ export const mapHookFileToGetComponent = async ( background: "#fff", color: "#000", padding: "8px 10px", - width: "200px", + minWidth: "200px", borderRadius: "0.375rem", display: "flex", alignItems: "center", diff --git a/src/components/tables/create.ts b/src/components/tables/create.ts index f4adf28..c7094f2 100644 --- a/src/components/tables/create.ts +++ b/src/components/tables/create.ts @@ -24,11 +24,20 @@ const mapColumns = ( + => { const { file: { fileName }, + entityName, } = hookFile; + const table = tables.find((table) => table.name === entityName); const componentName = `${fileName.replace("use", "")}`; + const primaryKeyColumn = table?.columns?.find( + (column) => column.name === table?.primaryKeys.at(0)?.name + ); + const content = ` ${comment} "use client"; import { FormEventHandler, MouseEventHandler, useState } from "react"; + import type { Row } from "../../hooks/${fileName}"; - export default function Delete${componentName}({ onDelete, onFetch }: { onDelete: (id: number) => Promise, onFetch: () => Promise }) { + export default function Delete${componentName}({ + onDelete, + onFetch + }: { + onDelete: (id: Row["id"]) => Promise, + onFetch: () => Promise + }) { const [message, setMessage] = useState(); const handleSubmit: FormEventHandler = (event) => { @@ -73,11 +88,20 @@ export const mapHookFileToDeleteComponent = async ( + mapHookFileToCreateComponent(tableHookFile, tables) ); - const deleteComponentPromises = tableHookFiles.map( - mapHookFileToDeleteComponent + const deleteComponentPromises = tableHookFiles.map((tableHookFile) => + mapHookFileToDeleteComponent(tableHookFile, tables) ); const files = await Promise.all([ diff --git a/src/pgMeta/fetchTables.ts b/src/pgMeta/fetchTables.ts index 7e8de22..3be7b2d 100644 --- a/src/pgMeta/fetchTables.ts +++ b/src/pgMeta/fetchTables.ts @@ -74,6 +74,7 @@ export async function fetchTables(): Promise<{ (table) => table.schema === "public" ); + // TODO: handle tables with a primary key column not named "id" const tables = publicTables.filter(({ primaryKeys }) => primaryKeys.some(({ name }) => name === "id") ); From 6d60dad97350ae1f12d910dbd09b9af08742694d Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Wed, 30 Aug 2023 10:23:00 +0100 Subject: [PATCH 7/7] generate put component --- src/components/tables/index.ts | 10 +- src/components/tables/update.ts | 208 ++++++++++++++++++++++++++++++++ src/hooks/table.ts | 49 ++++---- 3 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 src/components/tables/update.ts diff --git a/src/components/tables/index.ts b/src/components/tables/index.ts index 550d730..0df9ff5 100644 --- a/src/components/tables/index.ts +++ b/src/components/tables/index.ts @@ -4,6 +4,7 @@ import comment from "../../comment"; import type { File, HookFile } from "../../types"; import { parseNameFormats } from "../../utils"; import { mapHookFileToCreateComponent } from "./create"; +import { mapHookFileToUpdateComponent } from "./update"; import { mapHookFileToDeleteComponent } from "./delete"; import { mapHookFileToGetComponent } from "./get"; @@ -17,6 +18,7 @@ export const mapHookFileToComponent = async ( const componentName = fileName.replace("use", ""); const getComponentName = `Get${componentName}`; const createComponentName = `Create${componentName}`; + const updateComponentName = `Update${componentName}`; const deleteComponentName = `Delete${componentName}`; const { camelCasePlural, pascalCase, pascalCasePlural } = parseNameFormats(componentName); @@ -29,15 +31,17 @@ export const mapHookFileToComponent = async ( import ${fileName} from "../../hooks/${fileName}"; import ${getComponentName} from "./${getComponentName}"; import ${createComponentName} from "./${createComponentName}"; + import ${updateComponentName} from "./${updateComponentName}"; import ${deleteComponentName} from "./${deleteComponentName}"; export default function ${componentName}() { - const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, delete${pascalCase} } = ${fileName}(); + const { ${camelCasePlural}, fetch${pascalCasePlural}, create${pascalCase}, update${pascalCase}, delete${pascalCase} } = ${fileName}(); return (
<${getComponentName} ${camelCasePlural}={${camelCasePlural}} onFetch={fetch${pascalCasePlural}} /> <${createComponentName} onCreate={create${pascalCase}} onFetch={fetch${pascalCasePlural}} /> + <${updateComponentName} onUpdate={update${pascalCase}} onFetch={fetch${pascalCasePlural}} /> <${deleteComponentName} onDelete={delete${pascalCase}} onFetch={fetch${pascalCasePlural}} />
) @@ -67,6 +71,9 @@ export const parseComponentFilesForTables = async ( const createComponentPromises = tableHookFiles.map((tableHookFile) => mapHookFileToCreateComponent(tableHookFile, tables) ); + const updateComponentPromises = tableHookFiles.map((tableHookFile) => + mapHookFileToUpdateComponent(tableHookFile, tables) + ); const deleteComponentPromises = tableHookFiles.map((tableHookFile) => mapHookFileToDeleteComponent(tableHookFile, tables) ); @@ -74,6 +81,7 @@ export const parseComponentFilesForTables = async ( const files = await Promise.all([ ...componentPromises, ...createComponentPromises, + ...updateComponentPromises, ...getComponentPromises, ...deleteComponentPromises, ]); diff --git a/src/components/tables/update.ts b/src/components/tables/update.ts new file mode 100644 index 0000000..9b0e50f --- /dev/null +++ b/src/components/tables/update.ts @@ -0,0 +1,208 @@ +import prettier from "prettier"; +import comment from "../../comment"; +import type { ColumnResponse, TablesResponse } from "../../pgMeta/fetchTables"; +import type { File, HookFile } from "../../types"; +import { parseNameFormats } from "../../utils"; + +const mapColumns = ( + columns?: ColumnResponse[] +): { fields: string; inputs: string } => { + if (!columns) { + return { fields: "", inputs: "" }; + } + + const filteredColumns = columns.filter( + (column) => column.isIdentity || column.isUpdatable + ); + const fields = filteredColumns + .filter((column) => !column.isIdentity) + .map((column) => `"${column.name}"`) + .join(","); + const inputs = filteredColumns + .map((column, index) => { + const label = column.isIdentity ? `${column.name}*` : column.name; + return ` +
+ + + 0 ? 'marginTop: "10px",' : ""} + background: "#000", + color: "#fff", + border: "1px solid #34383A", + marginLeft: "10px", + flex: "1", + borderRadius: "0.375rem", + padding: "4px 16px", + }} + /> +
+ `; + }) + .join(" "); + + return { fields, inputs }; +}; + +export const mapHookFileToUpdateComponent = async ( + hookFile: HookFile, + tables: TablesResponse +): Promise => { + const { + entityName, + file: { fileName }, + } = hookFile; + + const table = tables.find((table) => table.name === entityName); + const componentName = `${fileName.replace("use", "")}`; + const { pascalCase } = parseNameFormats(componentName); + + const { fields, inputs } = mapColumns(table?.columns); + + const content = ` + ${comment} + + "use client"; + + import { FormEventHandler, MouseEventHandler, useState } from "react"; + import type { Row, Update${pascalCase} } from "../../hooks/${fileName}"; + + const fields: Array = [${fields}] + + export default function Update${componentName}({ + onUpdate, + onFetch + }: { + onUpdate: (id: Row["id"], updatedRow: Update${pascalCase}) => Promise, + onFetch: () => Promise + }) { + const [message, setMessage] = useState(); + + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); + const target = event.target as typeof event.target & Update${pascalCase}; + const id = (target["id"] as any)?.value; + const updatedRow = fields + .map((field) => ({ field, value: (target[field] as any)?.value })) + .reduce((newRow, { field,value }) => { + if (value.trim() !== "") { + newRow[field] = value; + } + return newRow; + }, {} as Record); + onUpdate(id, updatedRow) + .then((task) => { + if (task) { + setMessage("row with id " + task.id + " updated!"); + onFetch(); + } else { + setMessage("failed to update row!"); + } + }) + .catch((error) => { + if (error.message) { + setMessage(error.message); + } else { + setMessage("failed to update row!"); + } + }); + }; + + const handleClick: MouseEventHandler = () => { + setMessage(undefined); + } + + if (message) { + return
+ {message} + +
+ } + + return ( +
+ + ${inputs} + + +
+ ); + } + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + return { + fileName: `Update${componentName}.tsx`, + content: formattedContent, + }; +}; diff --git a/src/hooks/table.ts b/src/hooks/table.ts index 79fdf68..0daae99 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -29,17 +29,17 @@ const mapTableToFile = async (table: TableResponse): Promise => { }, []); const fetch${pascalCasePlural} = async() => { - try { - const { data, error } = await supabase - .from("${tableName}") - .select(); - if (error) { - throw error; - } - set${pascalCasePlural}(data || []); - } catch (error) { - console.error("Error fetching", error); + try { + const { data, error } = await supabase + .from("${tableName}") + .select(); + if (error) { + throw error; } + set${pascalCasePlural}(data || []); + } catch (error) { + console.error("Error fetching", error); + } }; const create${pascalCase} = async (newData: Insert${pascalCase}) => { @@ -55,23 +55,20 @@ const mapTableToFile = async (table: TableResponse): Promise => { }; const update${pascalCase} = async (id: Row["id"], updatedData: Update${pascalCase}) => { - try { - const { data, error } = await supabase - .from("${tableName}") - .update(updatedData) - .eq("id", id) - .select(); - if (error) { - throw error; - } - set${pascalCasePlural}( - ${camelCasePlural}.map((${camelCase}) => - ${camelCase}.id === id ? { ...${camelCase}, ...data[0] } : ${camelCase} - ) - ); - } catch (error) { - console.error("Error updating alert:", error); + const { data, error } = await supabase + .from("${tableName}") + .update(updatedData) + .eq("id", id) + .select(); + if (error) { + throw error; } + set${pascalCasePlural}( + ${camelCasePlural}.map((${camelCase}) => + ${camelCase}.id === id ? { ...${camelCase}, ...data[0] } : ${camelCase} + ) + ); + return data[0]; }; const delete${pascalCase} = async (id: Row["id"]): Promise => {