diff --git a/waspc/data/Generator/templates/react-app/src/crud/_crud.ts b/waspc/data/Generator/templates/sdk/crud/_crud.ts similarity index 92% rename from waspc/data/Generator/templates/react-app/src/crud/_crud.ts rename to waspc/data/Generator/templates/sdk/crud/_crud.ts index d2e66b58de..24a14bd26f 100644 --- a/waspc/data/Generator/templates/react-app/src/crud/_crud.ts +++ b/waspc/data/Generator/templates/sdk/crud/_crud.ts @@ -1,8 +1,8 @@ {{={= =}=}} -import { createAction } from "../actions/core"; -import { useAction } from "../actions"; -import { createQuery } from "../queries/core"; -import { useQuery } from "../queries"; +import { createAction } from "wasp/rpc/actions/core"; +import { useAction } from "wasp/rpc"; +import { createQuery } from "wasp/rpc/queries/core"; +import { useQuery } from "wasp/rpc"; import { {=# operations.Get =} GetQueryResolved, @@ -19,7 +19,7 @@ import { {=# operations.Delete =} DeleteActionResolved, {=/ operations.Delete =} -} from '../../../server/src/crud/{= name =}' +} from 'wasp/server/crud/{= name =}' function createCrud() { {=# operations.Get =} diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 90cadbfade..531b202f21 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -27,9 +27,13 @@ "./rpc": "./dist/rpc/index.js", {=! Used by users, documented. =} "./rpc/queries": "./dist/rpc/queries/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./rpc/queries/core": "./dist/rpc/queries/core.js", {=! Used by users, documented. =} "./rpc/actions": "./dist/rpc/actions/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} + "./rpc/actions/core": "./dist/rpc/actions/core.js", + {=! Used by our code, uncodumented (but accessible) for users. =} "./rpc/queryClient": "./dist/rpc/queryClient.js", {=! Used by users, documented. =} "./types": "./dist/types/index.js", @@ -117,6 +121,9 @@ {=! Used by users, documented. =} "./dbSeed/types": "./dist/dbSeed/types.js", {=! Used by users, documented. =} + "./crud/*": "./dist/crud/*.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./server/crud/*": "./dist/server/crud/*", "./email": "./dist/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./email/core/types": "./dist/email/core/types.js", diff --git a/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts new file mode 100644 index 0000000000..6fd34aea45 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts @@ -0,0 +1,105 @@ +{{={= =}=}} +import type { + {=# isAuthEnabled =} + AuthenticatedAction, + AuthenticatedQuery, + {=/ isAuthEnabled =} + {=^ isAuthEnabled =} + Action, + Query, + {=/ isAuthEnabled =} + _{= crud.entityUpper =}, +} from "wasp/server/_types"; +import type { Prisma } from "@prisma/client"; +import { Payload } from "wasp/server/_types/serialization"; +import type { + {= crud.entityUpper =}, +} from "wasp/entities"; +{=# overrides.GetAll.isDefined =} +{=& overrides.GetAll.importStatement =} +{=/ overrides.GetAll.isDefined =} +{=# overrides.Get.isDefined =} +{=& overrides.Get.importStatement =} +{=/ overrides.Get.isDefined =} +{=# overrides.Create.isDefined =} +{=& overrides.Create.importStatement =} +{=/ overrides.Create.isDefined =} +{=# overrides.Update.isDefined =} +{=& overrides.Update.importStatement =} +{=/ overrides.Update.isDefined =} +{=# overrides.Delete.isDefined =} +{=& overrides.Delete.importStatement =} +{=/ overrides.Delete.isDefined =} + +type _WaspEntityTagged = _{= crud.entityUpper =} +type _WaspEntity = {= crud.entityUpper =} + +{=# crud.operations.GetAll =} +// Get All query +export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.GetAll.isDefined =} +type GetAllInput = {} +type GetAllOutput = _WaspEntity[] +export type GetAllQueryResolved = GetAllQuery +{=/ overrides.GetAll.isDefined =} +{=# overrides.GetAll.isDefined =} +const _waspGetAllQuery = {= overrides.GetAll.importIdentifier =} +export type GetAllQueryResolved = typeof _waspGetAllQuery +{=/ overrides.GetAll.isDefined =} +{=/ crud.operations.GetAll =} + +{=# crud.operations.Get =} +// Get query +export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Get.isDefined =} +type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput +type GetOutput = _WaspEntity | null +export type GetQueryResolved = GetQuery +{=/ overrides.Get.isDefined =} +{=# overrides.Get.isDefined =} +const _waspGetQuery = {= overrides.Get.importIdentifier =} +export type GetQueryResolved = typeof _waspGetQuery +{=/ overrides.Get.isDefined =} +{=/ crud.operations.Get =} + +{=# crud.operations.Create =} +// Create action +export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Create.isDefined =} +type CreateInput = Prisma.{= crud.entityUpper =}CreateInput +type CreateOutput = _WaspEntity +export type CreateActionResolved = CreateAction +{=/ overrides.Create.isDefined =} +{=# overrides.Create.isDefined =} +const _waspCreateAction = {= overrides.Create.importIdentifier =} +export type CreateActionResolved = typeof _waspCreateAction +{=/ overrides.Create.isDefined =} +{=/ crud.operations.Create =} + +{=# crud.operations.Update =} +// Update action +export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Update.isDefined =} +type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput +type UpdateOutput = _WaspEntity +export type UpdateActionResolved = UpdateAction +{=/ overrides.Update.isDefined =} +{=# overrides.Update.isDefined =} +const _waspUpdateAction = {= overrides.Update.importIdentifier =} +export type UpdateActionResolved = typeof _waspUpdateAction +{=/ overrides.Update.isDefined =} +{=/ crud.operations.Update =} + +{=# crud.operations.Delete =} +// Delete action +export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Delete.isDefined =} +type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput +type DeleteOutput = _WaspEntity +export type DeleteActionResolved = DeleteAction +{=/ overrides.Delete.isDefined =} +{=# overrides.Delete.isDefined =} +const _waspDeleteAction = {= overrides.Delete.importIdentifier =} +export type DeleteActionResolved = typeof _waspDeleteAction +{=/ overrides.Delete.isDefined =} +{=/ crud.operations.Delete =} diff --git a/waspc/data/Generator/templates/server/src/crud/_operations.ts b/waspc/data/Generator/templates/server/src/crud/_operations.ts index 0185a6cfd7..31237b2dc9 100644 --- a/waspc/data/Generator/templates/server/src/crud/_operations.ts +++ b/waspc/data/Generator/templates/server/src/crud/_operations.ts @@ -1,25 +1,14 @@ {{={= =}=}} import prisma from "wasp/server/dbClient"; -import type { - {=# isAuthEnabled =} - AuthenticatedAction, - AuthenticatedQuery, - {=/ isAuthEnabled =} - {=^ isAuthEnabled =} - Action, - Query, - {=/ isAuthEnabled =} - _{= crud.entityUpper =}, -} from "../_types"; import type { Prisma } from "@prisma/client"; -import { Payload } from "../_types/serialization.js"; import type { {= crud.entityUpper =}, -} from "../entities"; +} from "wasp/entities"; {=# isAuthEnabled =} -import { throwInvalidCredentialsError } from '../auth/utils.js' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' {=/ isAuthEnabled =} +import type { GetAllQuery, GetQuery, CreateAction, UpdateAction, DeleteAction } from "{= crudTypesImportPath =}"; {=# overrides.GetAll.isDefined =} {=& overrides.GetAll.importStatement =} {=/ overrides.GetAll.isDefined =} @@ -36,7 +25,6 @@ import { throwInvalidCredentialsError } from '../auth/utils.js' {=& overrides.Delete.importStatement =} {=/ overrides.Delete.isDefined =} -type _WaspEntityTagged = _{= crud.entityUpper =} type _WaspEntity = {= crud.entityUpper =} const entities = { {= crud.entityUpper =}: prisma.{= crud.entityLower =}, @@ -48,12 +36,7 @@ const entities = { {=# crud.operations.GetAll =} // Get All query {=! -// 1. We define the type for the operation using "queryType" template variable which is either -// AuthenticatedQuery or Query (it depends on whether auth is enabled or not). -=} -export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> -{=! -// 2. Then, we either use the default implementation of the operation... +// 1. We either use the default implementation of the operation... =} {=^ overrides.GetAll.isDefined =} type GetAllInput = {} @@ -74,13 +57,7 @@ const _waspGetAllQuery = {= overrides.GetAll.importIdentifier =} {=/ overrides.GetAll.isDefined =} {=! -// 3. We then define the final type for the operation, which is the type of the function we defined in the previous step. -// It will pick up either the default implementation or the one from the overrides. -=} -export type GetAllQueryResolved = typeof _waspGetAllQuery - -{=! -// 4. We define a function that is used as the Express route handler +// 2. We define a function that is used as the Express route handler =} export async function getAllFn(args, context) { return (_waspGetAllQuery as any)(args, { @@ -95,7 +72,6 @@ export async function getAllFn(args, context) { {=# crud.operations.Get =} // Get query -export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Get.isDefined =} type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type GetOutput = _WaspEntity | null @@ -109,7 +85,6 @@ const _waspGetQuery: GetQuery = ((args, context) => { {=# overrides.Get.isDefined =} const _waspGetQuery = {= overrides.Get.importIdentifier =} {=/ overrides.Get.isDefined =} -export type GetQueryResolved = typeof _waspGetQuery export async function getFn(args, context) { return (_waspGetQuery as any)(args, { @@ -121,7 +96,6 @@ export async function getFn(args, context) { {=# crud.operations.Create =} // Create action -export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Create.isDefined =} type CreateInput = Prisma.{= crud.entityUpper =}CreateInput type CreateOutput = _WaspEntity @@ -136,8 +110,6 @@ const _waspCreateAction: CreateAction = ((args, conte const _waspCreateAction = {= overrides.Create.importIdentifier =} {=/ overrides.Create.isDefined =} -export type CreateActionResolved = typeof _waspCreateAction - export async function createFn(args, context) { return (_waspCreateAction as any)(args, { ...context, @@ -148,7 +120,6 @@ export async function createFn(args, context) { {=# crud.operations.Update =} // Update action -export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Update.isDefined =} type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput type UpdateOutput = _WaspEntity @@ -167,8 +138,6 @@ const _waspUpdateAction: UpdateAction = ((args, conte const _waspUpdateAction = {= overrides.Update.importIdentifier =} {=/ overrides.Update.isDefined =} -export type UpdateActionResolved = typeof _waspUpdateAction - export async function updateFn(args, context) { return (_waspUpdateAction as any)(args, { ...context, @@ -179,7 +148,6 @@ export async function updateFn(args, context) { {=# crud.operations.Delete =} // Delete action -export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Delete.isDefined =} type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type DeleteOutput = _WaspEntity @@ -195,8 +163,6 @@ const _waspDeleteAction: DeleteAction = ((args, conte const _waspDeleteAction = {= overrides.Delete.importIdentifier =} {=/ overrides.Delete.isDefined =} -export type DeleteActionResolved = typeof _waspDeleteAction - export async function deleteFn(args, context) { return (_waspDeleteAction as any)(args, { ...context, diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 509f8c604e..1f6d172974 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -16,7 +16,9 @@ "./core/auth": "./dist/core/auth.js", "./rpc": "./dist/rpc/index.js", "./rpc/queries": "./dist/rpc/queries/index.js", + "./rpc/queries/core": "./dist/rpc/queries/core.js", "./rpc/actions": "./dist/rpc/actions/index.js", + "./rpc/actions/core": "./dist/rpc/actions/core.js", "./rpc/queryClient": "./dist/rpc/queryClient.js", "./types": "./dist/types/index.js", "./auth": "./dist/auth/index.js", @@ -59,6 +61,8 @@ "./server/queries": "./dist/server/queries/index.js", "./server/auth/email": "./dist/server/auth/email/index.js", "./dbSeed/types": "./dist/dbSeed/types.js", + "./crud/*": "./dist/crud/*.js", + "./server/crud/*": "./dist/server/crud/*", "./email": "./dist/email/index.js", "./email/core/types": "./dist/email/core/types.js", "./server/auth/email/utils": "./dist/server/auth/email/utils.js", diff --git a/waspc/examples/todo-typescript/main.wasp b/waspc/examples/todo-typescript/main.wasp index 8a9f9a0187..4acb2bafa4 100644 --- a/waspc/examples/todo-typescript/main.wasp +++ b/waspc/examples/todo-typescript/main.wasp @@ -70,6 +70,19 @@ entity Task {=psl userId Int psl=} +crud Tasks { + entity: Task, + operations: { + getAll: { + overrideFn: import { getAllQuery } from "@src/task/crud.js", + }, + create: {}, + update: {}, + get: {}, + delete: {} + } +} + route RootRoute { path: "/", to: MainPage } page MainPage { authRequired: true, diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index 59ee858266..89d3ab2caa 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -1,34 +1,37 @@ -import "./Main.css"; -import React, { useEffect, FormEventHandler, FormEvent } from "react"; -import logout from "wasp/auth/logout"; -import { useQuery, useAction } from "wasp/rpc"; // Wasp uses a thin wrapper around react-query -import { getTasks } from "wasp/rpc/queries"; +import './Main.css' +import React, { useEffect, FormEventHandler, FormEvent } from 'react' +import logout from 'wasp/auth/logout' +import { useQuery, useAction } from 'wasp/rpc' // Wasp uses a thin wrapper around react-query +import { getTasks } from 'wasp/rpc/queries' import { createTask, updateTask, deleteTasks, customEmailSending, -} from "wasp/rpc/actions"; -import waspLogo from "./waspLogo.png"; -import type { Task } from "wasp/entities"; -import type { User } from "wasp/auth/types"; -import { getFirstProviderUserId } from "wasp/auth/user"; -import { Link } from "react-router-dom"; +} from 'wasp/rpc/actions' +import waspLogo from './waspLogo.png' +import type { Task } from 'wasp/entities' +import type { User } from 'wasp/auth/types' +import { getFirstProviderUserId } from 'wasp/auth/user' +import { Link } from 'react-router-dom' +import { Tasks } from 'wasp/crud/Tasks' // import login from "wasp/auth/login"; // import signup from "wasp/auth/signup"; import useAuth from 'wasp/auth/useAuth' export const MainPage = ({ user }: { user: User }) => { - const { data: tasks, isLoading, error } = useQuery(getTasks); + const { data: tasks, isLoading, error } = useQuery(getTasks) const { data: userAgain } = useAuth() - if (isLoading) return "Loading..."; - if (error) return "Error: " + error; + const { data: allTasks } = Tasks.getAll.useQuery() + + if (isLoading) return 'Loading...' + if (error) return 'Error: ' + error // console.log(login); // console.log(signup); - const completed = tasks?.filter((task) => task.isDone).map((task) => task.id); + const completed = tasks?.filter((task) => task.isDone).map((task) => task.id) return (
@@ -45,6 +48,8 @@ export const MainPage = ({ user }: { user: User }) => { )} {tasks && } +

All

+ {allTasks && }
- ); -}; + ) +} function Todo({ id, isDone, description }: Task) { const handleIsDoneChange: FormEventHandler = async ( @@ -68,11 +73,11 @@ function Todo({ id, isDone, description }: Task) { await updateTask({ id, isDone: event.currentTarget.checked, - }); + }) } catch (err: any) { - window.alert("Error while updating task " + err?.message); + window.alert('Error while updating task ' + err?.message) } - }; + } return (
  • @@ -87,38 +92,38 @@ function Todo({ id, isDone, description }: Task) {
  • - ); + ) } function TasksList({ tasks }: { tasks: Task[] }) { - if (tasks.length === 0) return

    No tasks yet.

    ; + if (tasks.length === 0) return

    No tasks yet.

    return (
      {tasks.map((task, idx) => ( ))}
    - ); + ) } function NewTaskForm() { const handleSubmit = async (event: FormEvent) => { - event.preventDefault(); + event.preventDefault() try { - const description = event.currentTarget.description.value; - console.log(description); - event.currentTarget.reset(); - await createTask({ description }); + const description = event.currentTarget.description.value + console.log(description) + event.currentTarget.reset() + await createTask({ description }) } catch (err: any) { - window.alert("Error: " + err?.message); + window.alert('Error: ' + err?.message) } - }; + } return (
    - ); + ) } diff --git a/waspc/examples/todo-typescript/src/task/crud.ts b/waspc/examples/todo-typescript/src/task/crud.ts new file mode 100644 index 0000000000..042cdf7563 --- /dev/null +++ b/waspc/examples/todo-typescript/src/task/crud.ts @@ -0,0 +1,6 @@ +import { Task } from "wasp/entities"; +import { GetAllQuery } from "wasp/server/crud/Tasks"; + +export const getAllQuery = ((args, context) => { + return context.entities.Task.findMany({}); +}) satisfies GetAllQuery<{}, Task[]>; diff --git a/waspc/src/Wasp/Generator/Common.hs b/waspc/src/Wasp/Generator/Common.hs index 5ce25639ca..cecdd011d8 100644 --- a/waspc/src/Wasp/Generator/Common.hs +++ b/waspc/src/Wasp/Generator/Common.hs @@ -10,14 +10,18 @@ module Wasp.Generator.Common makeJsonWithEntityData, GeneratedSrcDir, makeJsArrayFromHaskellList, + dropExtensionFromImportPath, ) where import Data.Aeson (KeyValue ((.=)), object) import qualified Data.Aeson as Aeson import Data.List (intercalate) -import StrongPath (Dir, Rel, reldir) +import Data.Maybe (fromJust) +import StrongPath (Dir, File, Path, Posix, Rel, reldir) +import qualified StrongPath as SP import StrongPath.Types (Path') +import System.FilePath (splitExtension) import Wasp.Generator.Templates (TemplatesDir) import qualified Wasp.SemanticVersion as SV import Wasp.Util (toLowerFirst) @@ -72,3 +76,8 @@ makeJsArrayFromHaskellList :: [String] -> String makeJsArrayFromHaskellList list = "[" ++ intercalate ", " listOfJsStrings ++ "]" where listOfJsStrings = map (\s -> "'" ++ s ++ "'") list + +dropExtensionFromImportPath :: Path Posix (Rel r) (File f) -> Path Posix (Rel r) (File f) +dropExtensionFromImportPath = fromJust . SP.parseRelFileP . dropExtension . SP.fromRelFileP + where + dropExtension = fst . splitExtension diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 3607d51788..9a9684d6ef 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -38,6 +38,7 @@ import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.ApiRoutesG (genApis) import Wasp.Generator.SdkGenerator.AuthG (genAuth) import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.CrudG (genCrud) import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) @@ -97,6 +98,7 @@ genSdkReal spec = <++> genUniversalDir <++> genExternalCodeDir (AS.externalCodeFiles spec) <++> genEntitiesAndServerTypesDirs spec + <++> genCrud spec <++> genJobTypes spec <++> genApis spec <++> genWebSockets spec diff --git a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs new file mode 100644 index 0000000000..6f3b08284c --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs @@ -0,0 +1,96 @@ +module Wasp.Generator.SdkGenerator.CrudG + ( genCrud, + getCrudTypesImportPathForName, + ) +where + +import Data.Aeson (KeyValue ((.=)), object) +import qualified Data.Aeson.Types as Aeson.Types +import Data.Maybe (fromJust) +import StrongPath + ( File', + Path, + Posix, + Rel, + reldir, + reldirP, + relfile, + (), + ) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec, getCruds) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import qualified Wasp.AppSpec.Crud as AS.Crud +import Wasp.AppSpec.Valid (getApp, getIdFieldFromCrudEntity, isAuthEnabled) +import Wasp.Generator.Crud (crudDeclarationToOperationsList, getCrudFilePath, getCrudOperationJson, makeCrudOperationKeyAndJsonPair) +import Wasp.Generator.FileDraft (FileDraft) +import qualified Wasp.Generator.JsImport as GJI +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.ServerOpsGenerator (extImportToJsImport) +import Wasp.Util ((<++>)) + +genCrud :: AppSpec -> Generator [FileDraft] +genCrud spec = + if areThereAnyCruds + then + genCrudOperations spec cruds + <++> genCrudServerOperations spec cruds + else return [] + where + cruds = getCruds spec + areThereAnyCruds = not $ null cruds + +genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] +genCrudOperations spec cruds = return $ map genCrudOperation cruds + where + genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft + genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) + where + tmplPath = [relfile|crud/_crud.ts|] + destPath = [reldir|crud|] fromJust (SP.parseRelFile (name ++ ".ts")) + tmplData = getCrudOperationJson name crud idField + idField = getIdFieldFromCrudEntity spec crud + +genCrudServerOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] +genCrudServerOperations spec cruds = return $ map genCrudOperation cruds + where + genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft + genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) + where + tmplPath = [relfile|server/crud/_operationTypes.ts|] + destPath = [reldir|server/crud|] getCrudFilePath name "ts" + tmplData = + object + [ "crud" .= getCrudOperationJson name crud idField, + "isAuthEnabled" .= isAuthEnabled spec, + "userEntityUpper" .= maybeUserEntity, + "overrides" .= object overrides, + "queryType" .= queryTsType, + "actionType" .= actionTsType + ] + idField = getIdFieldFromCrudEntity spec crud + maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth + maybeAuth = AS.App.auth $ snd $ getApp spec + + queryTsType :: String + queryTsType = if isAuthEnabled spec then "AuthenticatedQuery" else "Query" + + actionTsType :: String + actionTsType = if isAuthEnabled spec then "AuthenticatedAction" else "Action" + + overrides :: [Aeson.Types.Pair] + overrides = map operationToOverrideImport crudOperations + + crudOperations = crudDeclarationToOperationsList crud + + operationToOverrideImport :: (AS.Crud.CrudOperation, AS.Crud.CrudOperationOptions) -> Aeson.Types.Pair + operationToOverrideImport (operation, options) = makeCrudOperationKeyAndJsonPair operation importJson + where + importJson = GJI.jsImportToImportJson $ extImportToJsImport <$> AS.Crud.overrideFn options + +getCrudTypesImportPathForName :: String -> Path Posix (Rel r) File' +getCrudTypesImportPathForName crudName = makeSdkImportPath $ [reldirP|server/crud|] fromJust (SP.parseRelFileP crudName) diff --git a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs index 0b5689d582..bdf184a8dd 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs @@ -24,6 +24,7 @@ import Wasp.Generator.Crud import qualified Wasp.Generator.Crud.Routes as Routes import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.CrudG (getCrudTypesImportPathForName) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson) import Wasp.JsImport (JsImportPath (RelativeImportPath)) @@ -95,7 +96,8 @@ genCrudOperations spec cruds = return $ map genCrudOperation cruds "userEntityUpper" .= maybeUserEntity, "overrides" .= object overrides, "queryType" .= queryTsType, - "actionType" .= actionTsType + "actionType" .= actionTsType, + "crudTypesImportPath" .= SP.fromRelFileP (getCrudTypesImportPathForName name) ] idField = getIdFieldFromCrudEntity spec crud maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 63c68bc8d0..e48a9f8c52 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -43,7 +43,6 @@ import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.WebAppGenerator.AuthG (genAuth) import qualified Wasp.Generator.WebAppGenerator.Common as C -import Wasp.Generator.WebAppGenerator.CrudG (genCrud) import Wasp.Generator.WebAppGenerator.JsImport (extImportToImportJson) import Wasp.Generator.WebAppGenerator.RouterGenerator (genRouter) import qualified Wasp.Generator.WebSocket as AS.WS @@ -77,7 +76,6 @@ genWebApp spec = do <++> genPublicDir spec <++> genDotEnv spec <++> genEnvValidationScript - <++> genCrud spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs index ef001efdef..96420b860b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs @@ -28,10 +28,9 @@ module Wasp.Generator.WebAppGenerator.Common where import qualified Data.Aeson as Aeson -import Data.Maybe (fromJust, fromMaybe) +import Data.Maybe (fromMaybe) import StrongPath (Abs, Dir, File, File', Path, Path', Posix, Rel, absdirP, reldir, ()) import qualified StrongPath as SP -import System.FilePath (splitExtension) import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec.App as AS.App import qualified Wasp.AppSpec.App.Client as AS.App.Client @@ -44,6 +43,7 @@ import Wasp.Generator.Common ServerRootDir, UniversalTemplatesDir, WebAppRootDir, + dropExtensionFromImportPath, universalTemplatesDirInTemplatesDir, ) import Wasp.Generator.FileDraft (FileDraft, createCopyFileDraft, createTemplateFileDraft) @@ -138,9 +138,7 @@ mkUniversalTmplFdWithDst relSrcPath relDstPath = Nothing toViteImportPath :: Path Posix (Rel r) (File f) -> Path Posix (Rel r) (File f) -toViteImportPath = fromJust . SP.parseRelFileP . dropExtension . SP.fromRelFileP - where - dropExtension = fst . splitExtension +toViteImportPath = dropExtensionFromImportPath getBaseDir :: AppSpec -> Path Posix Abs (Dir ()) getBaseDir spec = fromMaybe [absdirP|/|] maybeBaseDir diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs deleted file mode 100644 index 397b85efed..0000000000 --- a/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs +++ /dev/null @@ -1,35 +0,0 @@ -module Wasp.Generator.WebAppGenerator.CrudG - ( genCrud, - ) -where - -import Data.Maybe (fromJust) -import StrongPath (reldir, relfile, ()) -import qualified StrongPath as SP -import Wasp.AppSpec (AppSpec, getCruds) -import qualified Wasp.AppSpec.Crud as AS.Crud -import Wasp.AppSpec.Valid (getIdFieldFromCrudEntity) -import Wasp.Generator.Crud (getCrudOperationJson) -import Wasp.Generator.FileDraft (FileDraft) -import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.WebAppGenerator.Common as C - -genCrud :: AppSpec -> Generator [FileDraft] -genCrud spec = - if areThereAnyCruds - then genCrudOperations spec cruds - else return [] - where - cruds = getCruds spec - areThereAnyCruds = not $ null cruds - -genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] -genCrudOperations spec cruds = return $ map genCrudOperation cruds - where - genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft - genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) - where - tmplPath = [relfile|src/crud/_crud.ts|] - destPath = C.webAppSrcDirInWebAppRootDir [reldir|crud|] fromJust (SP.parseRelFile (name ++ ".ts")) - tmplData = getCrudOperationJson name crud idField - idField = getIdFieldFromCrudEntity spec crud diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index e5995e29b7..9d24c8a987 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -294,6 +294,7 @@ library Wasp.Generator.SdkGenerator.ApiRoutesG Wasp.Generator.SdkGenerator.Auth.AuthFormsG Wasp.Generator.SdkGenerator.Auth.EmailAuthG + Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.Auth.LocalAuthG Wasp.Generator.SdkGenerator.Auth.OAuthAuthG Wasp.Generator.SdkGenerator.AuthG @@ -335,7 +336,6 @@ library Wasp.Generator.WebAppGenerator.Start Wasp.Generator.WebAppGenerator.Test Wasp.Generator.WebSocket - Wasp.Generator.WebAppGenerator.CrudG Wasp.Generator.WriteFileDrafts Wasp.JsImport Wasp.Message