diff --git a/waspc/data/Generator/templates/sdk/api/index.ts b/waspc/data/Generator/templates/sdk/api/index.ts index 8b22dd7ebc..971f010160 100644 --- a/waspc/data/Generator/templates/sdk/api/index.ts +++ b/waspc/data/Generator/templates/sdk/api/index.ts @@ -2,7 +2,7 @@ import axios, { type AxiosError } from 'axios' import config from 'wasp/core/config' import { storage } from 'wasp/core/storage' -import { apiEventsEmitter } from 'wasp/api/events' +import { apiEventsEmitter } from './events.js' const api = axios.create({ baseURL: config.apiUrl, diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index c27bc650c8..91de08a021 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -69,8 +69,8 @@ "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", {=! Used by users, documented. =} "./api": "./dist/api/index.js", - {=! Parts are used by users, documented. Parts are probably used by our code, undocumented (but accessible). =} - "./api/*": "./dist/api/*", + {=! Used by our framework code (Websockets), undocumented (but accessible) for users. =} + "./api/events": "./dist/api/events.js", {=! Used by users, documented. =} "./operations": "./dist/operations/index.js", {=! If we import a symbol like "import something form 'wasp/something'", we must =} diff --git a/waspc/data/Generator/templates/server/src/apis/types.ts b/waspc/data/Generator/templates/sdk/server/apis/types.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/apis/types.ts rename to waspc/data/Generator/templates/sdk/server/apis/types.ts diff --git a/waspc/data/Generator/templates/server/src/routes/apis/index.ts b/waspc/data/Generator/templates/server/src/routes/apis/index.ts index fe18c87761..3e87cd3643 100644 --- a/waspc/data/Generator/templates/server/src/routes/apis/index.ts +++ b/waspc/data/Generator/templates/server/src/routes/apis/index.ts @@ -5,7 +5,7 @@ import { handleRejection } from 'wasp/server/utils' import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js' {=# isAuthEnabled =} import auth from 'wasp/core/auth' -import { type SanitizedUser } from '../../_types' +import { type SanitizedUser } from 'wasp/server/_types' {=/ isAuthEnabled =} {=# apiNamespaces =} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts index 8b22dd7ebc..971f010160 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts @@ -2,7 +2,7 @@ import axios, { type AxiosError } from 'axios' import config from 'wasp/core/config' import { storage } from 'wasp/core/storage' -import { apiEventsEmitter } from 'wasp/api/events' +import { apiEventsEmitter } from './events.js' const api = axios.create({ baseURL: config.apiUrl, 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 1c885f9c00..290eff1d56 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -37,7 +37,7 @@ "./auth/forms/ResetPassword": "./dist/auth/forms/ResetPassword.jsx", "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", "./api": "./dist/api/index.js", - "./api/*": "./dist/api/*", + "./api/events": "./dist/api/events.js", "./operations": "./dist/operations/index.js", "./ext-src/*": "./dist/ext-src/*.js", "./operations/*": "./dist/operations/*", diff --git a/waspc/examples/todo-typescript/main.wasp b/waspc/examples/todo-typescript/main.wasp index 17aa6f7666..70f2b63374 100644 --- a/waspc/examples/todo-typescript/main.wasp +++ b/waspc/examples/todo-typescript/main.wasp @@ -8,8 +8,8 @@ app TodoTypescript { userEntity: User, methods: { usernameAndPassword: { - userSignupFields: import { googleUserSignupFields } from "@src/user/userSignupFields.js" - }, // this is a very naive implementation, use 'email' in production instead + userSignupFields: import { userSignupFields } from "@src/user/userSignupFields.js" + }, // google: { // userSignupFields: import { googleUserSignupFields } from "@src/user/userSignupFields.js" // }, @@ -116,3 +116,8 @@ action deleteTasks { fn: import { deleteTasks } from "@src/task/actions.js", entities: [Task], } + +api fooBar { + fn: import { fooBar } from "@src/api.js", + httpRoute: (GET, "/foo/bar") +} diff --git a/waspc/examples/todo-typescript/src/api.ts b/waspc/examples/todo-typescript/src/api.ts new file mode 100644 index 0000000000..5157cd7a59 --- /dev/null +++ b/waspc/examples/todo-typescript/src/api.ts @@ -0,0 +1,6 @@ +import type { FooBar } from "wasp/server/apis/types"; // This type is generated by Wasp based on the `api` declaration above. + +export const fooBar: FooBar = (req, res, context) => { + res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware. + res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); +}; diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 19e569bbad..c0d913a17a 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -35,6 +35,7 @@ import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed) import Wasp.Generator.Job.Process (runNodeCommandAsJob) import Wasp.Generator.Monad (Generator) 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.RpcGenerator (genRpc) @@ -91,6 +92,7 @@ genSdkReal spec = <++> genUniversalDir <++> genExternalCodeDir (AS.externalCodeFiles spec) <++> genEntitiesAndServerTypesDirs spec + <++> genApis spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs b/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs new file mode 100644 index 0000000000..c5f3f048b4 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs @@ -0,0 +1,59 @@ +module Wasp.Generator.SdkGenerator.ApiRoutesG + ( genApis, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import Data.List (nub) +import Data.Maybe (fromMaybe) +import StrongPath (File', Path', Rel, relfile) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec, getApis) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.Api as Api +import Wasp.AppSpec.Valid (isAuthEnabled) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.ServerGenerator.ApiRoutesG (getApiEntitiesObject, isAuthEnabledForApi) +import Wasp.Util (toUpperFirst) + +genApis :: AppSpec -> Generator [FileDraft] +genApis spec = + if areThereAnyCustomApiRoutes + then + sequence + [ genApiTypes spec + ] + else return [] + where + areThereAnyCustomApiRoutes = not . null $ getApis spec + +genApiTypes :: AppSpec -> Generator FileDraft +genApiTypes spec = + return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) + where + namedApis = AS.getApis spec + apis = snd <$> namedApis + tmplData = + object + [ "apiRoutes" .= map getTmplData namedApis, + "shouldImportAuthenticatedApi" .= any usesAuth apis, + "shouldImportNonAuthenticatedApi" .= not (all usesAuth apis), + "allEntities" .= nub (concatMap getApiEntitiesObject apis) + ] + usesAuth = fromMaybe (isAuthEnabledGlobally spec) . Api.auth + tmplFile = C.asTmplFile [relfile|server/apis/types.ts|] + dstFile = SP.castRel tmplFile :: Path' (Rel C.SdkRootDir) File' + + getTmplData :: (String, Api.Api) -> Aeson.Value + getTmplData (name, api) = + object + [ "typeName" .= toUpperFirst name, + "entities" .= getApiEntitiesObject api, + "usesAuth" .= isAuthEnabledForApi spec api + ] + + isAuthEnabledGlobally :: AppSpec -> Bool + isAuthEnabledGlobally = isAuthEnabled diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs index 1c743815ec..ed3cf975f0 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs @@ -13,6 +13,9 @@ data SdkRootDir data SdkTemplatesDir +asTmplFile :: Path' (Rel d) File' -> Path' (Rel SdkTemplatesDir) File' +asTmplFile = SP.castRel + mkTmplFdWithDstAndData :: Path' (Rel SdkTemplatesDir) File' -> Path' (Rel SdkRootDir) File' -> diff --git a/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs b/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs index 9962cf85da..14e55b68c3 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs @@ -1,12 +1,13 @@ module Wasp.Generator.ServerGenerator.ApiRoutesG ( genApis, + getApiEntitiesObject, + isAuthEnabledForApi, ) where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson import Data.Char (toLower) -import Data.List (nub) import Data.Maybe (fromMaybe, isJust) import StrongPath (Dir, File', Path, Path', Posix, Rel, reldirP, relfile) import qualified StrongPath as SP @@ -20,15 +21,13 @@ import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (getAliasedJsImportStmtAndIdentifier) -import Wasp.Util (toUpperFirst) genApis :: AppSpec -> Generator [FileDraft] genApis spec = if areThereAnyCustomApiRoutes then sequence - [ genApiRoutes spec, - genApiTypes spec + [ genApiRoutes spec ] else return [] where @@ -88,31 +87,6 @@ genApiRoutes spec = relPathFromApisRoutesToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir) relPathFromApisRoutesToServerSrcDir = [reldirP|../..|] -genApiTypes :: AppSpec -> Generator FileDraft -genApiTypes spec = - return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - namedApis = AS.getApis spec - apis = snd <$> namedApis - tmplData = - object - [ "apiRoutes" .= map getTmplData namedApis, - "shouldImportAuthenticatedApi" .= any usesAuth apis, - "shouldImportNonAuthenticatedApi" .= not (all usesAuth apis), - "allEntities" .= nub (concatMap getApiEntitiesObject apis) - ] - usesAuth = fromMaybe (isAuthEnabledGlobally spec) . Api.auth - tmplFile = C.asTmplFile [relfile|src/apis/types.ts|] - dstFile = SP.castRel tmplFile :: Path' (Rel ServerRootDir) File' - - getTmplData :: (String, Api.Api) -> Aeson.Value - getTmplData (name, api) = - object - [ "typeName" .= toUpperFirst name, - "entities" .= getApiEntitiesObject api, - "usesAuth" .= isAuthEnabledForApi spec api - ] - getApiEntitiesObject :: Api.Api -> [Aeson.Value] getApiEntitiesObject api = maybe [] (map (makeJsonWithEntityData . AS.refName)) (Api.entities api) diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 4bc413a3a5..445658eba9 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -291,14 +291,15 @@ library Wasp.Generator.NpmDependencies Wasp.Generator.NpmInstall Wasp.Generator.SdkGenerator - Wasp.Generator.SdkGenerator.Common - Wasp.Generator.SdkGenerator.ServerOpsGenerator - Wasp.Generator.SdkGenerator.AuthG + Wasp.Generator.SdkGenerator.ApiRoutesG Wasp.Generator.SdkGenerator.Auth.AuthFormsG - Wasp.Generator.SdkGenerator.Auth.OAuthAuthG - Wasp.Generator.SdkGenerator.Auth.LocalAuthG Wasp.Generator.SdkGenerator.Auth.EmailAuthG + Wasp.Generator.SdkGenerator.Auth.LocalAuthG + Wasp.Generator.SdkGenerator.Auth.OAuthAuthG + Wasp.Generator.SdkGenerator.AuthG + Wasp.Generator.SdkGenerator.Common Wasp.Generator.SdkGenerator.RpcGenerator + Wasp.Generator.SdkGenerator.ServerOpsGenerator Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG