├── nix ├── pkgs │ ├── reflex │ │ └── default.nix │ ├── arionEvaluate │ │ └── default.nix │ ├── makeDockerComposeFile │ │ └── default.nix │ ├── mylib │ │ ├── composeAll.nix │ │ ├── default.nix │ │ └── exportEnvsCommand.nix │ ├── nix-gitignore │ │ ├── default.nix │ │ ├── update.sh │ │ └── revision.json │ ├── morph │ │ ├── update.sh │ │ ├── revision.json │ │ └── default.nix │ ├── shmig │ │ ├── update.sh │ │ └── revision.json │ ├── nixform │ │ ├── update.sh │ │ ├── revision.json │ │ └── default.nix │ ├── ntmuxp │ │ ├── update.sh │ │ ├── revision.json │ │ └── default.nix │ ├── pgtest │ │ ├── update.sh │ │ ├── default.nix │ │ └── revision.json │ ├── pnpm2nix │ │ ├── update.sh │ │ ├── default.nix │ │ └── revision.json │ ├── waitforit │ │ ├── revisionDarwin.json │ │ ├── revisionLinux.json │ │ ├── default.nix │ │ └── update.sh │ ├── pnpm │ │ ├── default.nix │ │ ├── revision.json │ │ └── update.sh │ ├── yarn2nix │ │ ├── default.nix │ │ ├── revision.json │ │ └── update.sh │ ├── arion │ │ ├── revision.json │ │ ├── default.nix │ │ └── update.sh │ ├── docker-volume-rm-if-exists │ │ ├── docker-volume-rm-if-exists │ │ └── default.nix │ ├── writeShellScript │ │ └── default.nix │ ├── opensslDecrypt │ │ └── default.nix │ ├── dump-schema │ │ ├── default.nix │ │ └── dump-schema │ ├── wait-for-postgres │ │ ├── default.nix │ │ └── wait-for-postgres │ ├── readRevision │ │ └── default.nix │ ├── pg_prove │ │ └── default.nix │ └── gitCryptUnlock │ │ └── default.nix ├── overlays.nix ├── pkgs.nix ├── nixpkgs │ ├── revision.json │ ├── update.sh │ └── default.nix └── pkgsLinux.nix ├── .gitattributes ├── config ├── public │ ├── region.nix │ ├── database.nix │ ├── dhparams.pem │ └── keys.nix └── ignored │ ├── graphile-license.example │ ├── github-oauth.nix.example │ ├── aws.nix.example │ ├── passwords-dev.setup.sh │ └── passwords.setup.sh ├── packages ├── client │ ├── NextjsApp │ │ ├── PagesCustom │ │ │ ├── Error.purs_ │ │ │ └── NotFound.purs_ │ │ ├── Pages │ │ │ ├── Examples │ │ │ │ ├── Ace.scss │ │ │ │ ├── HigherOrderComponents.deps.js │ │ │ │ ├── HigherOrderComponents.scss │ │ │ │ ├── Lazy.purs │ │ │ │ ├── Ace.purs │ │ │ │ ├── Basic.purs │ │ │ │ ├── Interpret.purs │ │ │ │ ├── Lifecycle.purs │ │ │ │ ├── TextNodes.purs │ │ │ │ ├── DeeplyNested.purs │ │ │ │ ├── ComponentsInputs.purs │ │ │ │ ├── KeyboardInput.purs │ │ │ │ ├── Components.purs │ │ │ │ ├── ComponentsMultitype.purs │ │ │ │ ├── HigherOrderComponents.purs │ │ │ │ ├── EffectsAffAjax.purs │ │ │ │ ├── EffectsEffectRandom.purs │ │ │ │ └── Ace.deps.js │ │ │ ├── Login.purs │ │ │ └── Register.purs │ │ ├── Blocks │ │ │ ├── PurescriptLogo.js │ │ │ ├── PurescriptLogo.purs │ │ │ └── PurescriptLogo.svg │ │ ├── NodeEnv.js │ │ ├── PageImplementations │ │ │ ├── Login │ │ │ │ ├── Css.js │ │ │ │ ├── Css.purs │ │ │ │ ├── Css.module.scss │ │ │ │ └── Types.purs │ │ │ ├── Register │ │ │ │ ├── Css.js │ │ │ │ ├── Css.purs │ │ │ │ ├── Css.module.scss │ │ │ │ └── Types.purs │ │ │ ├── VerifyUserEmailMobile │ │ │ │ ├── Css.js │ │ │ │ ├── Css.module.scss │ │ │ │ ├── Css.purs │ │ │ │ └── Types.purs │ │ │ ├── VerifyUserEmailWeb │ │ │ │ ├── Css.js │ │ │ │ ├── Css.module.scss │ │ │ │ ├── Css.purs │ │ │ │ └── Types.purs │ │ │ ├── Secret │ │ │ │ └── Types.purs │ │ │ └── Secret.purs │ │ ├── Constants.purs │ │ ├── Link │ │ │ ├── Types.purs │ │ │ └── Lib.purs │ │ ├── Navigate │ │ │ ├── Mobile.purs │ │ │ └── Client.purs │ │ ├── Manifest │ │ │ ├── PageManifest.purs │ │ │ └── ClientPagesManifest.purs │ │ ├── Navigate.purs │ │ ├── Router │ │ │ └── Server.purs │ │ ├── NodeEnv.purs │ │ ├── Queries │ │ │ └── IsUsernameOrEmailInUse.purs │ │ └── Data │ │ │ └── InUseUsernameOrEmail.purs │ ├── client-and-mobile-deps.js │ ├── server.entry.js │ ├── mobile.entry.js │ ├── client.entry.js │ ├── client-and-mobile-deps.scss │ └── NextjsGraphqlApi │ │ ├── Object │ │ ├── WebLoginPayload.purs │ │ ├── WebRegisterPayload.purs │ │ ├── PostsEdge.purs │ │ ├── UsersEdge.purs │ │ ├── UserAuthenticationsEdge.purs │ │ └── PageInfo.purs │ │ ├── Scopes.purs │ │ └── Subscription.purs ├── src │ ├── Mjml.js │ ├── Firstline.js │ ├── LoaderUtils.js │ ├── EmailValidator.js │ ├── ExpressSession.js │ ├── EmailValidator.purs │ ├── Webpack │ │ ├── Compiler.js │ │ ├── Plugins.js │ │ ├── GetError.purs │ │ ├── Plugins.purs │ │ ├── FFI.js │ │ └── FFI.purs │ ├── GraphileWorker.js │ ├── PassportGithub.js │ ├── ConnectPgSimple.js │ ├── Firstline.purs │ ├── Cordova │ │ ├── EventTypes │ │ │ ├── Network.purs │ │ │ └── Battery.purs │ │ └── EventTypes.purs │ ├── Chokidar.js │ ├── HeterogeneousExtraShow.purs │ ├── RecursiveReaddirAsync.js │ ├── NodeUrlExtra.purs │ ├── WebpackSpagoLoader.js │ ├── LoaderUtils.purs │ ├── ForeignObjectExtra.purs │ ├── ConnectPgSimple.purs │ ├── OptparseExtra.purs │ ├── SpecAssertsExtra.purs │ ├── Favicons.js │ ├── WebpackSpagoLoader.purs │ ├── ContribWebpackPlugins.purs │ ├── SimpleLookupEnv.purs │ ├── RunExtra.purs │ ├── Chokidar.purs │ ├── Data │ │ └── Email.purs │ ├── HalogenElementsExtra.purs │ ├── HalogenVdomStringRendererRaw.purs │ ├── ContribWebpackPlugins.js │ ├── BuildSeleniumChromeDriver.purs_ │ ├── Postgraphile.js │ ├── ArgonautCodecDecodersExtran.purs │ ├── ReadWriteStdinYesNo.purs_ │ ├── TimeExtra.purs │ ├── FRPEventExtra.purs │ └── SimpleXMLWithIndentation.purs ├── client-halogen-examples │ ├── lazy │ │ ├── README.md │ │ ├── .gitignore │ │ └── src │ │ │ ├── LazyLoadedImportImplementation.js │ │ │ ├── LazyLoaded.purs │ │ │ ├── LazyLoadedImport.purs │ │ │ └── LazyLoadedImport.js │ ├── ace │ │ ├── .gitignore │ │ ├── spago.dhall │ │ └── README.md │ ├── text-nodes │ │ ├── README.md │ │ └── .gitignore │ ├── components │ │ ├── .gitignore │ │ ├── spago.dhall │ │ ├── src │ │ │ └── Main.purs │ │ └── README.md │ ├── interpret │ │ ├── .gitignore │ │ ├── spago.dhall │ │ └── README.md │ ├── lifecycle │ │ ├── .gitignore │ │ ├── spago.dhall │ │ └── README.md │ ├── deeply-nested │ │ ├── .gitignore │ │ ├── src │ │ │ ├── Main.purs │ │ │ ├── C.purs │ │ │ ├── E.purs │ │ │ ├── F.purs │ │ │ ├── D.purs │ │ │ ├── A.purs │ │ │ └── B.purs │ │ └── README.md │ ├── effects-aff-ajax │ │ ├── .gitignore │ │ ├── spago.dhall │ │ ├── src │ │ │ └── Main.purs │ │ └── README.md │ ├── keyboard-input │ │ ├── .gitignore │ │ ├── spago.dhall │ │ └── README.md │ ├── components-multitype │ │ ├── .gitignore │ │ ├── spago.dhall │ │ ├── src │ │ │ └── Main.purs │ │ └── README.md │ ├── effects-effect-random │ │ ├── .gitignore │ │ ├── spago.dhall │ │ ├── src │ │ │ └── Main.purs │ │ └── README.md │ ├── higher-order-components │ │ ├── .gitignore │ │ ├── spago.dhall │ │ └── src │ │ │ └── Main.purs │ ├── basic │ │ ├── README.md │ │ └── src │ │ │ └── Button.purs │ └── components-inputs │ │ ├── README.md │ │ └── src │ │ └── Display.purs ├── api-server-config │ └── ApiServerConfig.purs ├── client-tests │ └── Test │ │ └── Main.purs ├── db-tests │ ├── extensions │ │ ├── random_boolean.sql │ │ ├── random_string.sql │ │ ├── random_email.sql │ │ ├── random_between.sql │ │ ├── has_column.sql │ │ ├── allow_app_roles_execute_pgtap_functions.sql │ │ ├── random_enum.sql │ │ └── randomly_set_empty_jwt_user_id.sql_ │ ├── tests-todo │ │ └── tables │ │ │ ├── users │ │ │ ├── select │ │ │ │ ├── app_user.sql │ │ │ │ └── app_owner.sql │ │ │ ├── insert │ │ │ │ └── app_owner.sql │ │ │ ├── delete │ │ │ │ └── app_owner.sql │ │ │ ├── priviliges │ │ │ │ ├── app_user.sql │ │ │ │ └── app_owner.sql │ │ │ └── update │ │ │ │ └── app_owner.sql │ │ │ ├── posts │ │ │ ├── priviligies │ │ │ │ ├── app_user.sql │ │ │ │ ├── app_owner.sql │ │ │ │ └── app_admin.sql │ │ │ └── common.sql │ │ │ └── users_oauths │ │ │ └── priviliges │ │ │ ├── app_user.sql │ │ │ └── app_owner.sql │ └── tests │ │ ├── roles │ │ ├── app_anonymous.sql │ │ ├── app_user.sql │ │ ├── app_admin.sql │ │ └── app_owner.sql │ │ ├── actions │ │ ├── confirm │ │ │ └── priviliges.sql_ │ │ ├── reset_password │ │ │ ├── priviliges.sql_ │ │ │ └── error_when_token_not_exists.sql_ │ │ ├── register │ │ │ ├── priviliges.sql_ │ │ │ ├── success_when_user_not_exists.sql_ │ │ │ └── error_when_email_already_registered.sql_ │ │ ├── resend_confirmation │ │ │ ├── priviliges.sql_ │ │ │ └── success.sql_ │ │ ├── send_reset_password │ │ │ ├── priviliges.sql_ │ │ │ └── error_when_email_not_exists.sql_ │ │ ├── login_or_register_oauth │ │ │ └── priviliges.sql_ │ │ ├── web_login │ │ │ ├── priviliges.sql │ │ │ ├── error_when_password_is_wrong.sql_ │ │ │ ├── error_when_email_is_wrong.sql_ │ │ │ ├── success_by_username_when_password_is_valid.sql │ │ │ └── success_by_email_when_password_is_valid.sql │ │ └── really_create_user │ │ │ ├── priviliges.sql │ │ │ └── success_not_verified.sql │ │ ├── queries │ │ ├── current_user │ │ │ └── priviliges.sql │ │ └── user_by_username_or_verified_email │ │ │ ├── priviliges.sql │ │ │ ├── success_by_username.sql │ │ │ └── success_by_email.sql │ │ ├── schemas │ │ ├── app_hidden.sql │ │ └── app_private.sql │ │ └── no_missing_indexes_on_foreign_keys.sql ├── worker │ └── Worker │ │ ├── JobIds.purs │ │ ├── EmailUI.purs │ │ ├── Types.purs │ │ └── Config │ │ └── FromEnv.purs ├── api-server │ └── ApiServer │ │ ├── PostgraphilePassportAuthPlugin │ │ ├── Shared.js │ │ └── Shared.purs │ │ ├── FrontendServerHttpProxy.purs_ │ │ ├── PostgraphilePassportAuthPlugin.js │ │ └── FrontendServerHttpProxy.js_ ├── client-webpack │ ├── README.md │ ├── cordova-webpack.config.js │ └── NextjsWebpack │ │ ├── WebpackConfig │ │ ├── Types.purs │ │ └── SpagoOptions.purs │ │ ├── BuildManifestPlugin.js │ │ └── FaviconsConfig.purs └── feature-tests │ └── FeatureTests │ ├── FeatureTestSpecUtils │ ├── AffRetry.purs │ ├── EmailAVar.purs │ └── LunaparkUtils.purs │ ├── Tests │ └── Login │ │ └── ErrorNotConfirmedSpec.purs_ │ └── AllTests.purs ├── migrations ├── 0000000001-start │ ├── 99_jwt.up.sql_ │ ├── 06_is_between_0_and_100.up.sql │ ├── 03_current_user_id_required.up.sql │ ├── 02_current_user_id_or_null.up.sql │ ├── 05_generate_url_safe_token.up.sql │ ├── 01_prelude.up.sql │ └── 04_tg__set_updated_at.up.sql ├── 0000000003-posts │ └── 01_posts │ │ ├── 02_triggers.up.sql │ │ ├── 03_policies.up.sql │ │ └── 01_table.up.sql ├── 0000000003-posts.sql ├── 0000000002-users │ ├── 03_app_public.functions.current_user.up.sql │ ├── 07_app_private.tables.user_authentication_secrets.up.sql │ ├── 16_app_private.tables.user_sessions.up.sql │ ├── 01_app_private.tables.user_secrets.up.sql │ └── 09_app_public.functions.user_by_username_or_email.up.sql └── 0000000001-start.sql ├── packages.dhall ├── spago.dhall ├── docs ├── on-cordova-development.md ├── TODO.md └── inspiration.md ├── regenerate-graphql-api-purs-codegen.sh ├── spago-worker.dhall ├── docker └── common │ ├── postgres.nix │ ├── pgadmin.nix │ ├── db_tests.nix │ ├── server.nix │ └── migrator.nix ├── plugins ├── fetch.json └── android.json ├── spago-api-server.dhall ├── spago-feature-tests.dhall ├── .gitignore ├── postcss.config.js ├── dev-commands ├── all-commands.nix └── dev │ └── lib.nix └── regenerate-purs-files.sh /nix/pkgs/reflex/default.nix: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /docs/bundle.js binary 2 | -------------------------------------------------------------------------------- /config/public/region.nix: -------------------------------------------------------------------------------- 1 | "us-west-1" 2 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PagesCustom/Error.purs_: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PagesCustom/NotFound.purs_: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/ignored/graphile-license.example: -------------------------------------------------------------------------------- 1 | ............ 2 | -------------------------------------------------------------------------------- /packages/src/Mjml.js: -------------------------------------------------------------------------------- 1 | exports._mjml2html = require('mjml') 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/README.md: -------------------------------------------------------------------------------- 1 | # Text nodes 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/ace/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/text-nodes/README.md: -------------------------------------------------------------------------------- 1 | # Text nodes 2 | -------------------------------------------------------------------------------- /packages/src/Firstline.js: -------------------------------------------------------------------------------- 1 | exports._firstline = require('firstline') 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/interpret/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lifecycle/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/text-nodes/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-aff-ajax/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/keyboard-input/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-multitype/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-effect-random/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /packages/client/client-and-mobile-deps.js: -------------------------------------------------------------------------------- 1 | import "./client-and-mobile-deps.scss" 2 | -------------------------------------------------------------------------------- /packages/src/LoaderUtils.js: -------------------------------------------------------------------------------- 1 | exports._getOptions = require('loader-utils').getOptions 2 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/higher-order-components/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/example.js 2 | -------------------------------------------------------------------------------- /config/ignored/github-oauth.nix.example: -------------------------------------------------------------------------------- 1 | { 2 | CLIENT_ID=""; 3 | CLIENT_SECRET=""; 4 | } 5 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Ace.scss: -------------------------------------------------------------------------------- 1 | .ace_editor { 2 | height: 200px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/src/EmailValidator.js: -------------------------------------------------------------------------------- 1 | exports.emailValidate = require('email-validator').validate 2 | -------------------------------------------------------------------------------- /config/ignored/aws.nix.example: -------------------------------------------------------------------------------- 1 | { 2 | EC2_ACCESS_KEY = "..."; 3 | EC2_SECRET_KEY = "..."; 4 | } 5 | -------------------------------------------------------------------------------- /packages/client/server.entry.js: -------------------------------------------------------------------------------- 1 | import { main } from "./NextjsApp/Entries/Server.purs" 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/HigherOrderComponents.deps.js: -------------------------------------------------------------------------------- 1 | import './HigherOrderComponents.scss' 2 | -------------------------------------------------------------------------------- /packages/src/ExpressSession.js: -------------------------------------------------------------------------------- 1 | exports.expressSession = function() { 2 | return require("express-session") 3 | } 4 | -------------------------------------------------------------------------------- /migrations/0000000001-start/99_jwt.up.sql_: -------------------------------------------------------------------------------- 1 | create type app_public.jwt as ( 2 | role text, 3 | user_id uuid 4 | ); 5 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | /home/srghma/projects/my-purescript-package-sets/mypackages.dhall 3 | in upstream 4 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Blocks/PurescriptLogo.js: -------------------------------------------------------------------------------- 1 | exports.purescriptLogoSrc = require('./PurescriptLogo.svg').default 2 | -------------------------------------------------------------------------------- /nix/pkgs/arionEvaluate/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, arion }: 2 | 3 | import "${pkgs.arion}/share/arion/nix/eval-composition.nix" 4 | -------------------------------------------------------------------------------- /packages/src/EmailValidator.purs: -------------------------------------------------------------------------------- 1 | module EmailValidator where 2 | 3 | foreign import emailValidate :: String -> Boolean 4 | 5 | -------------------------------------------------------------------------------- /packages/client/mobile.entry.js: -------------------------------------------------------------------------------- 1 | import "./client-and-mobile-deps" 2 | import { main } from "./NextjsApp/Entries/Mobile.purs" 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /config/public/database.nix: -------------------------------------------------------------------------------- 1 | { 2 | POSTGRES_USER = "app_admin"; # superuser, only for running migrations 3 | DATABASE_NAME = "nextjsdemo"; 4 | } 5 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Blocks/PurescriptLogo.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Blocks.PurescriptLogo where 2 | 3 | foreign import purescriptLogoSrc :: String 4 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/NodeEnv.js: -------------------------------------------------------------------------------- 1 | 2 | exports.env = { 3 | apiUrl: process.env.apiUrl, 4 | isProduction: process.env.isProduction, 5 | } 6 | -------------------------------------------------------------------------------- /packages/client/client.entry.js: -------------------------------------------------------------------------------- 1 | import "./client-and-mobile-deps" 2 | import { main } from "./NextjsApp/Entries/Client.purs" 3 | 4 | main() 5 | 6 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "all" 2 | , dependencies = ./dependencies.dhall 3 | , packages = ./packages.dhall 4 | , sources = 5 | [ "./packages/**/*.purs" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/src/Webpack/Compiler.js: -------------------------------------------------------------------------------- 1 | exports._webpack = require('webpack') 2 | 3 | exports._webpackCompilerRun = function(compiler, handler) { 4 | compiler.run(handler) 5 | } 6 | -------------------------------------------------------------------------------- /nix/overlays.nix: -------------------------------------------------------------------------------- 1 | [ 2 | # (pkgs: pkgsOld: { 3 | # nodejs = pkgsOld.nodejs-14_x; 4 | # nodePackages = pkgsOld.nodePackages_14_x; 5 | # }) 6 | (import ./pkgs/overlay.nix) 7 | ] 8 | -------------------------------------------------------------------------------- /nix/pkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ./nixpkgs; 3 | 4 | config = { allowUnfree = true; }; 5 | 6 | overlays = import ./overlays.nix; 7 | in import nixpkgs { inherit config overlays; } 8 | -------------------------------------------------------------------------------- /packages/api-server-config/ApiServerConfig.purs: -------------------------------------------------------------------------------- 1 | module ApiServerConfig where 2 | 3 | expressSessionMiddleware_cookieName :: String 4 | expressSessionMiddleware_cookieName = "connect.sid" 5 | 6 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Login/Css.js: -------------------------------------------------------------------------------- 1 | // Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | exports.styles = require('./Css.module.scss').default 3 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Register/Css.js: -------------------------------------------------------------------------------- 1 | // Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | exports.styles = require('./Css.module.scss').default 3 | -------------------------------------------------------------------------------- /migrations/0000000003-posts/01_posts/02_triggers.up.sql: -------------------------------------------------------------------------------- 1 | create trigger _100_timestamps 2 | before update on app_public.posts 3 | for each row 4 | execute function app_private.tg__set_updated_at(); 5 | -------------------------------------------------------------------------------- /docs/on-cordova-development.md: -------------------------------------------------------------------------------- 1 | # how to console.log to logcat 2 | 3 | ```purs 4 | traceM (Data.Argonaut.Core.stringifyWithSpace 2 (Unsafe.Coerce.unsafeCoerce { message: "newRoute", xxx: show xxx })) 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailMobile/Css.js: -------------------------------------------------------------------------------- 1 | // Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | exports.styles = require('./Css.module.scss').default 3 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailWeb/Css.js: -------------------------------------------------------------------------------- 1 | // Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | exports.styles = require('./Css.module.scss').default 3 | -------------------------------------------------------------------------------- /packages/client/client-and-mobile-deps.scss: -------------------------------------------------------------------------------- 1 | @use "material-components-web/material-components-web"; 2 | 3 | :root { 4 | --mdc-theme-primary: #00c6ff; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } 10 | -------------------------------------------------------------------------------- /nix/pkgs/makeDockerComposeFile/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | expression: 4 | 5 | pkgs.writeTextFile { 6 | name = "docker-compose.yaml"; 7 | text = builtins.toJSON expression; # yaml is subset of json 8 | } 9 | -------------------------------------------------------------------------------- /nix/pkgs/mylib/composeAll.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | compose = f: g: x: f (g x); 8 | id = x: x; 9 | composeAll = builtins.foldl' compose id; 10 | 11 | in 12 | 13 | composeAll 14 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Constants.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Constants where 2 | 3 | pagesManifestId :: String 4 | pagesManifestId = "#__PAGES_MANIFEST__" 5 | 6 | pagesDataId :: String 7 | pagesDataId = "#__PAGE_DATA__" 8 | -------------------------------------------------------------------------------- /packages/src/GraphileWorker.js: -------------------------------------------------------------------------------- 1 | exports._run = require("graphile-worker").run 2 | exports._quickAddJob = require("graphile-worker").quickAddJob 3 | exports._stop = function(runner) { return runner.stop() } 4 | -------------------------------------------------------------------------------- /nix/nixpkgs/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "nixos", 3 | "repo": "nixpkgs-channels", 4 | "rev": "84d74ae9c9cbed73274b8e4e00be14688ffc93fe", 5 | "sha256": "03nfq5g4h7vlq431vhfaxz12nkm0qsd56fppn11yjl2wx92b5b4j" 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/ace/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/ace/**/*.purs" ], 5 | dependencies = config.dependencies # [ "ace" ] 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/components/**/*.purs" ], 5 | dependencies = config.dependencies 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lifecycle/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/lifecycle/**/*.purs" ], 5 | dependencies = config.dependencies 6 | } 7 | -------------------------------------------------------------------------------- /nix/pkgs/nix-gitignore/default.nix: -------------------------------------------------------------------------------- 1 | { runCommand, fetchFromGitHub, lib, readRevision, ... }: 2 | 3 | let 4 | src = fetchFromGitHub ( 5 | readRevision ./revision.json 6 | ); 7 | in 8 | import src { inherit lib runCommand; } 9 | -------------------------------------------------------------------------------- /packages/client-tests/Test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Effect (Effect) 5 | import Effect.Console (log) 6 | 7 | main :: Effect Unit 8 | main = do 9 | log "You should add some tests." 10 | -------------------------------------------------------------------------------- /nix/pkgs/morph/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/DBCDK/morph > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /nix/pkgs/shmig/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/mbucc/shmig > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/keyboard-input/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/keyboard-input/**/*.purs" ], 5 | dependencies = config.dependencies 6 | } 7 | -------------------------------------------------------------------------------- /packages/src/PassportGithub.js: -------------------------------------------------------------------------------- 1 | exports._passportStrategyGithub = function(options, verify) { 2 | return new (require('passport-github').Strategy)( 3 | Object.assign({ passReqToCallback: true }, options), 4 | verify 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /nix/pkgs/nixform/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/srghma/nixform > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /nix/pkgs/ntmuxp/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/srghma/ntmuxp > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /nix/pkgs/pgtest/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/srghma/pgtest > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/interpret/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/interpret/**/*.purs" ], 5 | dependencies = config.dependencies # [ "affjax" ] 6 | } 7 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/random_boolean.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | create or replace function random_boolean() returns boolean as $$ 5 | begin 6 | return random() > 0.5; 7 | end; 8 | $$ language plpgsql strict; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/random_string.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | create or replace function random_string() returns text as $$ 5 | begin 6 | return md5(random()::text); 7 | end; 8 | $$ language plpgsql strict; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /nix/pkgs/pgtest/default.nix: -------------------------------------------------------------------------------- 1 | { fetchFromGitHub, lib, readRevision }: 2 | 3 | let 4 | revData = readRevision ./revision.json; 5 | 6 | src = fetchFromGitHub { 7 | inherit (revData) rev sha256 owner repo; 8 | }; 9 | in 10 | src 11 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm2nix/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/adisbladis/pnpm2nix > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /nix/pkgs/waitforit/revisionDarwin.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v2.4.1", 3 | "sha256": "0r69dbwcm4bc92x4xclkp00dzpmwi0ggc3fly9gqwvzh5xs6g8r9", 4 | "url": "https://github.com/maxcnunes/waitforit/releases/download/v2.4.1/waitforit-darwin_amd64" 5 | } 6 | -------------------------------------------------------------------------------- /nix/pkgs/waitforit/revisionLinux.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v2.4.1", 3 | "sha256": "04fvl4wpmlvfgq2s8dqsjh63vjwb6hiw3397x72dy54ala1vfs4m", 4 | "url": "https://github.com/maxcnunes/waitforit/releases/download/v2.4.1/waitforit-linux_amd64" 5 | } 6 | -------------------------------------------------------------------------------- /packages/src/ConnectPgSimple.js: -------------------------------------------------------------------------------- 1 | exports.mkExpressSessionStore = function(expressSession) { 2 | return function(config) { 3 | const PgSession = require("connect-pg-simple")(expressSession) 4 | 5 | return new PgSession(config) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/nix-gitignore/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/siers/nix-gitignore > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-multitype/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/components-multitype/src/**/*.purs" ], 5 | dependencies = config.dependencies 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-aff-ajax/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/effects-aff-ajax/**/*.purs" ], 5 | dependencies = config.dependencies # [ "affjax" ] 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/higher-order-components/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/higher-order-components/**/*.purs" ], 5 | dependencies = config.dependencies 6 | } 7 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/src/LazyLoadedImportImplementation.js: -------------------------------------------------------------------------------- 1 | exports.lazyLoadedImport = function() { 2 | // or can use require.ensure 3 | var x = import( 4 | './LazyLoaded.purs' 5 | ) 6 | console.log(x) 7 | return x 8 | } 9 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/random_email.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | create or replace function random_email() returns text as $$ 5 | begin 6 | return md5(random()::text) || '@mail.com'; 7 | end; 8 | $$ language plpgsql strict; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /nix/pkgs/mylib/default.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | rec { 4 | recursiveMerge = import ./recursiveMerge.nix { inherit lib; }; 5 | composeAll = import ./composeAll.nix { inherit lib; }; 6 | exportEnvsCommand = import ./exportEnvsCommand.nix { inherit lib; }; 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm2nix/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, fetchFromGitHub, readRevision, ... }: 2 | 3 | let 4 | src = fetchFromGitHub ( 5 | readRevision ./revision.json 6 | ); 7 | 8 | pnpm2nix = pkgs.callPackage "${src}/default.nix" {}; 9 | in 10 | pnpm2nix 11 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-effect-random/spago.dhall: -------------------------------------------------------------------------------- 1 | let config = ../../spago.dhall 2 | 3 | in config // { 4 | sources = config.sources # [ "examples/effects-effect-random/**/*.purs" ], 5 | dependencies = config.dependencies # [ "random" ] 6 | } 7 | -------------------------------------------------------------------------------- /packages/src/Firstline.purs: -------------------------------------------------------------------------------- 1 | module Firstline where 2 | 3 | import Control.Promise (Promise) 4 | import Effect.Uncurried (EffectFn1) 5 | import Protolude 6 | import Pathy (Abs, File, Path) 7 | 8 | foreign import _firstline :: EffectFn1 String (Promise String) 9 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # phone number as username + sms 6-digit code 2 | 3 | https://youtu.be/CZ-Ahh6v0PY 4 | https://github.com/firebase/FirebaseUI-Android 5 | https://github.com/firebase/FirebaseUI-IOS 6 | https://www.youtube.com/watch?v=3tlSUMsEqAA&ab_channel=SamarthAgarwal 7 | -------------------------------------------------------------------------------- /packages/src/Cordova/EventTypes/Network.purs: -------------------------------------------------------------------------------- 1 | module Cordova.EventTypes.Network where 2 | 3 | import Web.Event.Event (EventType(..)) 4 | 5 | online :: EventType 6 | online = EventType "online" 7 | 8 | offline :: EventType 9 | offline = EventType "offline" 10 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/random_between.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | create or replace function random_between(low int, high int) returns int as $$ 5 | begin 6 | return floor(random()* (high-low + 1) + low); 7 | end; 8 | $$ language plpgsql strict; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm/default.nix: -------------------------------------------------------------------------------- 1 | { fetchFromGitHub, readRevision, ... }: 2 | 3 | let 4 | nixpkgs = fetchFromGitHub ( 5 | readRevision ./revision.json 6 | ); 7 | 8 | pkgs = import nixpkgs { config = { allowUnfree = true; }; }; 9 | in 10 | pkgs.nodePackages_10_x.pnpm 11 | -------------------------------------------------------------------------------- /nix/pkgs/yarn2nix/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, fetchFromGitHub, readRevision, ... }: 2 | 3 | let 4 | src = fetchFromGitHub ( 5 | readRevision ./revision.json 6 | ); 7 | 8 | # src = ~/projects/yarn2nix; 9 | 10 | src_ = import src { inherit pkgs; }; 11 | in 12 | src_ 13 | -------------------------------------------------------------------------------- /nix/pkgs/morph/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/DBCDK/morph", 3 | "rev": "c048d6339f18613a1544bc62ff852cb4c6de1042", 4 | "date": "2020-09-08T16:17:32+02:00", 5 | "sha256": "0yb8prji2nqjsj1aiiqnbqaajbi5l17rg8k78ry7pl3a8sqa3h1x", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/ntmuxp/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/srghma/ntmuxp", 3 | "rev": "e755165d84a2d1c93809a83e9e02d8539a0b627a", 4 | "date": "2019-08-01T10:52:48+03:00", 5 | "sha256": "0qrjaxmqr7zidr8h460115dba7jkr6rd403lzsxgmdmmwifkjwzc", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/pgtest/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/srghma/pgtest", 3 | "rev": "d01ecaf5a7442fec2ed36e76e5f68b90194f57a1", 4 | "date": "2019-01-12T11:03:24+02:00", 5 | "sha256": "17qmw2k4h8hz9ijmdskgkf8hv5jmzwfwhkm9n46fv3srz8j2q64h", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/srghma/nixpkgs", 3 | "rev": "7ea948f912efda9b85b3f6ed1ad9810ff995b298", 4 | "date": "2019-01-26T19:26:17+02:00", 5 | "sha256": "0q6b6415v4bgrh8mha2v2xsrr5qv823f06z7zi3q3jjls6mbliln", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/shmig/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/mbucc/shmig", 3 | "rev": "81006b75e31b0772d68f4e988194c4eb33f0c4eb", 4 | "date": "2019-06-09T10:38:26-04:00", 5 | "sha256": "0zd1yqink2kq7i68hi03723dgwp7p7y3phvs8js3rxsz4va6chgc", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailWeb/Css.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | 6 | max-width: 30%; 7 | min-width: 400px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | -------------------------------------------------------------------------------- /packages/src/Chokidar.js: -------------------------------------------------------------------------------- 1 | exports._watch = function({ files, onAll }) { 2 | const watcher = require('chokidar').watch(files) 3 | 4 | watcher.on('all', function(event, path) { 5 | onAll(path) 6 | }) 7 | 8 | return function() { 9 | watcher.close() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /nix/pkgs/arion/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/hercules-ci/arion", 3 | "rev": "427a3b0e3c453a29876962d0e415e757e50d6ed7", 4 | "date": "2020-10-02T13:00:10+02:00", 5 | "sha256": "0b5dvbadwaykc7xl7glfzd6pr9dfsw6m3dq438289xc6zsq6qs9r", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/mylib/exportEnvsCommand.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | exportEnvsCommand = attrs: builtins.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "export " + name + "=" + builtins.toString value) attrs); 8 | 9 | in 10 | 11 | exportEnvsCommand 12 | -------------------------------------------------------------------------------- /nix/pkgs/nixform/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/srghma/nixform", 3 | "rev": "3f091c9df7dcc2bfae3ea94549aee67b605b4497", 4 | "date": "2018-09-22T10:38:55+03:00", 5 | "sha256": "1wbcyna67kjxlxi8m5wg1zq56i58zrw6k3v2czpnjpi8y8ibgc4j", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/yarn2nix/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/moretea/yarn2nix", 3 | "rev": "37c3b7c1d1170ed6e063b804c4dbe871f4043349", 4 | "date": "2020-07-16T14:12:19+00:00", 5 | "sha256": "0z1gx5mqsi0ny3lcdvq4rvjr3bnp84pkfz7mdpcza6b4vmigmpxn", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailMobile/Css.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | 6 | max-width: 30%; 7 | min-width: 400px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | -------------------------------------------------------------------------------- /packages/src/HeterogeneousExtraShow.purs: -------------------------------------------------------------------------------- 1 | module HeterogeneousExtraShow where 2 | 3 | import Protolude 4 | import Heterogeneous.Mapping (class Mapping) 5 | 6 | data Show = Show 7 | 8 | instance showMappingAction :: (Show n) => Mapping Show n String where 9 | mapping Show = show 10 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm2nix/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/adisbladis/pnpm2nix", 3 | "rev": "f67be0925a91b92f54d99dbdead7a06920b979ac", 4 | "date": "2020-07-10T15:56:51+00:00", 5 | "sha256": "1qn21p3n02asz3h6llavxkjm845dj5w4q88ndgrxzww0xm1r8wsi", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/morph/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, fetchFromGitHub, readRevision, ... }: 2 | 3 | let 4 | revData = readRevision ./revision.json; 5 | 6 | src = fetchFromGitHub { 7 | inherit (revData) rev sha256 owner repo; 8 | }; 9 | in 10 | pkgs.callPackage "${src}/nix-packaging/default.nix" { } 11 | -------------------------------------------------------------------------------- /nix/pkgs/nix-gitignore/revision.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/siers/nix-gitignore", 3 | "rev": "a9609739209cef90e2e8eaa0b5b656721d3da63e", 4 | "date": "2020-05-02T18:37:36+03:00", 5 | "sha256": "1h7dgiy0kiyzsv0s2w5p6z5103k4s8wc6i793cwq1srk2kwdsrxm", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/pkgs/pnpm/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/srghma/nixpkgs --rev 7ea948f912efda9b85b3f6ed1ad9810ff995b298 --no-deepClone > "$SCRIPT_DIR/revision.json" 6 | -------------------------------------------------------------------------------- /nix/pkgs/docker-volume-rm-if-exists/docker-volume-rm-if-exists: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | volume="$1" 4 | 5 | if docker volume list | grep "$volume" &>/dev/null; then 6 | echo "removing volume $volume" 7 | docker volume rm "$volume" 8 | else 9 | echo "volume $volume don't exists, ignoring" 10 | fi 11 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Link/Types.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Link.Types where 2 | 3 | import Halogen as H 4 | import NextjsApp.Route as NextjsApp.Route 5 | import Web.UIEvent.MouseEvent as Web.UIEvent.MouseEvent 6 | import Protolude 7 | 8 | type Query 9 | = Const Void 10 | 11 | type Message 12 | = Void 13 | -------------------------------------------------------------------------------- /packages/worker/Worker/JobIds.purs: -------------------------------------------------------------------------------- 1 | module Worker.JobIds where 2 | 3 | -- generated by ./gen-job-ids-from-migrations.hs 4 | 5 | type JobIds a = 6 | { "JOB__SEND_PASSWORD_CHANGED_EMAIL" :: a 7 | , "JOB__SEND_PASSWORD_RESET_EMAIL" :: a 8 | , "JOB__SEND_VERIFICATION_EMAIL_FOR_USER_EMAIL" :: a 9 | } 10 | -------------------------------------------------------------------------------- /migrations/0000000001-start/06_is_between_0_and_100.up.sql: -------------------------------------------------------------------------------- 1 | create or replace function app_hidden.is_from_0_to_100( 2 | num decimal(3,0) 3 | ) returns boolean as $$ 4 | select (num >= 0) AND (num <= 100); 5 | $$ immutable strict language sql; 6 | 7 | grant execute on function app_hidden.is_from_0_to_100(decimal) to public; 8 | -------------------------------------------------------------------------------- /packages/src/RecursiveReaddirAsync.js: -------------------------------------------------------------------------------- 1 | const rra = require('recursive-readdir-async') 2 | 3 | exports._recursiveTreeList = function(pagesDir, options) { 4 | return rra.list( 5 | pagesDir, 6 | { 7 | mode: rra.TREE, 8 | recursive: true, 9 | include: options.include, 10 | } 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailWeb/Css.purs: -------------------------------------------------------------------------------- 1 | -- Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | module NextjsApp.PageImplementations.VerifyUserEmailWeb.Css (styles) where 3 | 4 | import Halogen.HTML (ClassName) 5 | 6 | foreign import styles :: 7 | { root :: ClassName 8 | } 9 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailMobile/Css.purs: -------------------------------------------------------------------------------- 1 | -- Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | module NextjsApp.PageImplementations.VerifyUserEmailMobile.Css (styles) where 3 | 4 | import Halogen.HTML (ClassName) 5 | 6 | foreign import styles :: 7 | { root :: ClassName 8 | } 9 | -------------------------------------------------------------------------------- /nix/pkgs/writeShellScript/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | 3 | name: text: 4 | pkgs.writeTextFile { 5 | inherit name; 6 | 7 | executable = true; 8 | 9 | text = '' 10 | #!${pkgs.stdenv.shell} -eu 11 | ${text} 12 | ''; 13 | 14 | checkPhase = '' 15 | ${pkgs.stdenv.shell} -n $out 16 | ''; 17 | } 18 | -------------------------------------------------------------------------------- /packages/src/NodeUrlExtra.purs: -------------------------------------------------------------------------------- 1 | module NodeUrlExtra where 2 | 3 | import Node.URL (Query) 4 | import Foreign.Object (Object) 5 | import Unsafe.Coerce (unsafeCoerce) 6 | 7 | -- | Should be one level 8 | toQuery :: Object String -> Query 9 | toQuery = unsafeCoerce 10 | 11 | fromQuery :: Query -> Object String 12 | fromQuery = unsafeCoerce 13 | -------------------------------------------------------------------------------- /packages/api-server/ApiServer/PostgraphilePassportAuthPlugin/Shared.js: -------------------------------------------------------------------------------- 1 | exports.mkSelectGraphQLResultFromTable = (user_id, sql) => 2 | (tableAlias, sqlBuilder) => { 3 | sqlBuilder.where( 4 | sql.fragment`${tableAlias}.id = ${sql.value(user_id)}` 5 | ) 6 | } 7 | 8 | exports.appPublicUsersFragment = fragment => fragment`app_public.users` 9 | -------------------------------------------------------------------------------- /nix/pkgs/opensslDecrypt/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | keyFile: name: ciphertext: 4 | pkgs.lib.removeSuffix "\n" ( 5 | builtins.readFile ( 6 | pkgs.runCommand name {} '' 7 | echo "${ciphertext}" | \ 8 | ${pkgs.openssl}/bin/openssl enc -aes-256-cbc -a -d -kfile "${keyFile}" -out "$out" 9 | '' 10 | ) 11 | ) 12 | -------------------------------------------------------------------------------- /packages/src/WebpackSpagoLoader.js: -------------------------------------------------------------------------------- 1 | exports.getAbsoluteOutputDirFromSpago = require('webpack-spago-loader/lib/getAbsoluteOutputDirFromSpago') 2 | 3 | exports.getSourcesFromSpago = require('webpack-spago-loader/lib/getSourcesFromSpago') 4 | 5 | exports.rules = require('webpack-spago-loader/rules') 6 | 7 | exports.watcherJob = require('webpack-spago-loader/rules') 8 | -------------------------------------------------------------------------------- /regenerate-graphql-api-purs-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | ################################### 6 | 7 | rm -rfd ./packages/client/NextjsGraphqlApi 8 | 9 | ./node_modules/.bin/purescript-graphql-client-generator \ 10 | --input-json ./schemas/schema.json --output ./packages/client/NextjsGraphqlApi --api NextjsGraphqlApi 11 | -------------------------------------------------------------------------------- /spago-worker.dhall: -------------------------------------------------------------------------------- 1 | { name = "worker" 2 | , dependencies = ./dependencies.dhall 3 | , packages = ./packages.dhall 4 | , sources = 5 | [ "./packages/src/**/*.purs" 6 | , "./packages/worker/**/*.purs" 7 | , "./packages/client/NextjsApp/AppM.purs" 8 | , "./packages/client/NextjsApp/Route.purs" 9 | , "./packages/client/NextjsApp/Link/Types.purs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /docker/common/postgres.nix: -------------------------------------------------------------------------------- 1 | {}: 2 | 3 | { 4 | image = "postgres:10"; 5 | 6 | volumes = [ 7 | "postgres_data:/var/lib/postgresql/data" 8 | ]; 9 | 10 | environment = { 11 | POSTGRES_USER = "app_admin"; # superuser, only for running migrations 12 | POSTGRES_PASSWORD = "app_admin_pass"; 13 | POSTGRES_DB = "nextjsdemo_test"; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /nix/pkgs/yarn2nix/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | nix-prefetch-git https://github.com/moretea/yarn2nix > "$SCRIPT_DIR/revision.json" 6 | # nix-prefetch-git https://github.com/srghma/yarn2nix --rev f559fc67c80204931444227d95f93d02161f2570 > "$SCRIPT_DIR/revision.json" 7 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/has_column.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | -- has_column( schema, table, column ) 5 | CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME ) 6 | RETURNS TEXT AS $$ 7 | SELECT ok( _cexists( $1, $2, $3 ), 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' should exist' ); 8 | $$ LANGUAGE SQL; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /packages/src/LoaderUtils.purs: -------------------------------------------------------------------------------- 1 | module LoaderUtils where 2 | 3 | import Effect.Uncurried (EffectFn1, runEffectFn1) 4 | import Protolude 5 | import Webpack.Loader (LoaderContext) 6 | import Node.URL (Query) 7 | 8 | foreign import _getOptions :: EffectFn1 LoaderContext Query 9 | 10 | getOptions :: LoaderContext -> Effect Query 11 | getOptions = runEffectFn1 _getOptions 12 | -------------------------------------------------------------------------------- /packages/src/Cordova/EventTypes/Battery.purs: -------------------------------------------------------------------------------- 1 | module Cordova.EventTypes.Battery where 2 | 3 | import Web.Event.Event (EventType(..)) 4 | 5 | batterycritical :: EventType 6 | batterycritical = EventType "batterycritical" 7 | 8 | batterylow :: EventType 9 | batterylow = EventType "batterylow" 10 | 11 | batterystatus :: EventType 12 | batterystatus = EventType "batterystatus" 13 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Example.DeeplyNested.A as A 7 | import Halogen.Aff as HA 8 | import Halogen.VDom.Driver (runUI) 9 | 10 | main :: Effect Unit 11 | main = HA.runHalogenAff do 12 | body <- HA.awaitBody 13 | runUI A.component unit body 14 | -------------------------------------------------------------------------------- /migrations/0000000003-posts.sql: -------------------------------------------------------------------------------- 1 | -- generated by ./gen-migrations.hs 2 | 3 | -- ==== UP ==== 4 | 5 | begin; 6 | 7 | \include 0000000003-posts/01_posts/01_table.up.sql 8 | \include 0000000003-posts/01_posts/02_triggers.up.sql 9 | \include 0000000003-posts/01_posts/03_policies.up.sql 10 | 11 | 12 | commit; 13 | 14 | -- ==== DOWN ==== 15 | 16 | -- do nothing, this should never happen 17 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/allow_app_roles_execute_pgtap_functions.sql: -------------------------------------------------------------------------------- 1 | 2 | -- allow our application roles execute pg_tap functions (like foreign_tables_are) 3 | grant usage on schema public to app_user, app_anonymous; 4 | grant select, insert, update, delete on all tables in schema public to app_user, app_anonymous; 5 | grant execute on all functions in schema public to app_user, app_anonymous; 6 | -------------------------------------------------------------------------------- /packages/src/ForeignObjectExtra.purs: -------------------------------------------------------------------------------- 1 | module ForeignObjectExtra where 2 | 3 | import Protolude 4 | import Foreign.Object (Object) 5 | import Foreign.Object as Object 6 | 7 | updateKeys :: forall a . (String -> String) -> Object a -> Object a 8 | updateKeys f o = 9 | (Object.toUnfoldable o :: Array (Tuple String a)) 10 | # map (\(Tuple k v) -> Tuple (f k) v) 11 | # Object.fromFoldable 12 | -------------------------------------------------------------------------------- /packages/src/ConnectPgSimple.purs: -------------------------------------------------------------------------------- 1 | module ConnectPgSimple where 2 | 3 | import ExpressSession (ExpressSession, ExpressSessionStore) 4 | 5 | import Database.PostgreSQL (Pool) 6 | 7 | type Config = 8 | { pool :: Pool 9 | , schemaName :: String 10 | , tableName :: String 11 | -- | , ttl 12 | } 13 | 14 | foreign import mkExpressSessionStore :: ExpressSession -> Config -> ExpressSessionStore 15 | -------------------------------------------------------------------------------- /packages/client-webpack/README.md: -------------------------------------------------------------------------------- 1 | # how webpack-spago-loader is being used 2 | 3 | on build (client and server) - the `webpack-spago-loader/build-job` one-off task is being used 4 | on dev (client and server) - the `webpack-spago-loader/watcher-job` watcher is being used 5 | 6 | -- on build and dev for mobile - the plugin is used because we run webpack through cordova and there is no way to check is it one time 7 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Navigate/Mobile.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Navigate.Mobile where 2 | 3 | import Protolude 4 | 5 | import FRP.Event (EventIO) 6 | 7 | import NextjsApp.Route as NextjsApp.Route 8 | 9 | navigate :: EventIO (Variant NextjsApp.Route.WebRoutesWithParamRow) -> (Variant NextjsApp.Route.WebRoutesWithParamRow) -> Effect Unit 10 | navigate newRouteEventIO route = newRouteEventIO.push route 11 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/higher-order-components/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.HOC.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Example.HOC.Harness as Harness 7 | import Halogen.Aff as HA 8 | import Halogen.VDom.Driver (runUI) 9 | 10 | main :: Effect Unit 11 | main = HA.runHalogenAff do 12 | body <- HA.awaitBody 13 | runUI Harness.component unit body 14 | -------------------------------------------------------------------------------- /packages/feature-tests/FeatureTests/FeatureTestSpecUtils/AffRetry.purs: -------------------------------------------------------------------------------- 1 | module FeatureTests.FeatureTestSpecUtils.AffRetry where 2 | 3 | import Prelude 4 | import Effect.Aff.Retry as AffRetry 5 | import Data.Time.Duration 6 | 7 | retryAction action = 8 | AffRetry.recovering 9 | (AffRetry.constantDelay (Milliseconds 200.0) <> AffRetry.limitRetries 10) 10 | [\_ _ -> pure true] 11 | (\_ -> action) 12 | -------------------------------------------------------------------------------- /migrations/0000000002-users/03_app_public.functions.current_user.up.sql: -------------------------------------------------------------------------------- 1 | create function app_public.current_user() returns app_public.users as $$ 2 | select users.* 3 | from app_public.users 4 | where id = app_public.current_user_id_or_null(); 5 | $$ language sql stable set search_path from current; 6 | 7 | comment on function app_public.current_user() is E'The currently logged in user (or null if not logged in).'; 8 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.Components.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Example.Components.Container as Container 7 | import Halogen.Aff as HA 8 | import Halogen.VDom.Driver (runUI) 9 | 10 | main :: Effect Unit 11 | main = HA.runHalogenAff do 12 | body <- HA.awaitBody 13 | runUI Container.component unit body 14 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-effect-random/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.Effects.Effect.Random.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Example.Effects.Effect.Random.Component (component) 7 | import Halogen.Aff as HA 8 | import Halogen.VDom.Driver (runUI) 9 | 10 | main :: Effect Unit 11 | main = HA.runHalogenAff do 12 | body <- HA.awaitBody 13 | runUI component unit body 14 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/HigherOrderComponents.scss: -------------------------------------------------------------------------------- 1 | .Panel { 2 | border: 1px solid #ccc; 3 | border-radius: 2px; 4 | margin: 1em; 5 | border-radius: 0.2em; 6 | } 7 | 8 | .Panel--open { 9 | border-radius: 0.2em 0.2em 0.5em 0.5em; 10 | } 11 | 12 | .Panel-header { 13 | background: #ddd; 14 | padding: 0.5em; 15 | text-align: right; 16 | } 17 | 18 | .Panel-content { 19 | padding: 0.5em; 20 | } 21 | -------------------------------------------------------------------------------- /nix/pkgs/nixform/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | terraform = pkgs.terraform.withPlugins (p: with p; [ aws tls local ]); 5 | 6 | revData = builtins.fromJSON (builtins.readFile ./revision.json); 7 | 8 | src = builtins.fetchTarball { 9 | inherit (revData) sha256; 10 | url = "${revData.url}/archive/${revData.rev}.tar.gz"; 11 | }; 12 | in 13 | pkgs.callPackage "${src}/default.nix" { inherit terraform; } 14 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-aff-ajax/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.Effects.Aff.Ajax.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Example.Effects.Aff.Ajax.Component (component) 7 | import Halogen.Aff as HA 8 | import Halogen.VDom.Driver (runUI) 9 | 10 | -- | Run the app. 11 | main :: Effect Unit 12 | main = HA.runHalogenAff do 13 | body <- HA.awaitBody 14 | runUI component unit body 15 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Login/Css.purs: -------------------------------------------------------------------------------- 1 | -- Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | module NextjsApp.PageImplementations.Login.Css (styles) where 3 | 4 | import Halogen.HTML (ClassName) 5 | 6 | foreign import styles :: 7 | { root :: ClassName 8 | , logo :: ClassName 9 | , buttons :: ClassName 10 | , buttons__button :: ClassName 11 | , input :: ClassName 12 | } 13 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Register/Css.purs: -------------------------------------------------------------------------------- 1 | -- Do not edit, this file was autogenerated by generate-halogen-css-modules 2 | module NextjsApp.PageImplementations.Register.Css (styles) where 3 | 4 | import Halogen.HTML (ClassName) 5 | 6 | foreign import styles :: 7 | { root :: ClassName 8 | , logo :: ClassName 9 | , buttons :: ClassName 10 | , buttons__button :: ClassName 11 | , input :: ClassName 12 | } 13 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-multitype/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Example.Components.Multitype.Main where 2 | 3 | import Prelude 4 | import Effect (Effect) 5 | import Halogen.Aff as HA 6 | import Halogen.VDom.Driver (runUI) 7 | import Example.Components.Multitype.Container as Container 8 | 9 | main :: Effect Unit 10 | main = HA.runHalogenAff do 11 | body <- HA.awaitBody 12 | runUI Container.component unit body 13 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Manifest/PageManifest.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Manifest.PageManifest where 2 | 3 | import Protolude 4 | import Data.Array as Data.Array 5 | 6 | type PageManifest 7 | = { css :: Array String 8 | , js :: Array String 9 | } 10 | 11 | mergePageManifests :: PageManifest -> PageManifest -> PageManifest 12 | mergePageManifests a b = { css: Data.Array.nubEq (a.css <> b.css), js: Data.Array.nubEq (a.js <> b.js) } 13 | -------------------------------------------------------------------------------- /plugins/fetch.json: -------------------------------------------------------------------------------- 1 | { 2 | "cordova-plugin-whitelist": { 3 | "source": { 4 | "type": "registry", 5 | "id": "cordova-plugin-whitelist@^1.3.4" 6 | }, 7 | "is_top_level": true, 8 | "variables": {} 9 | }, 10 | "cordova-plugin-webpack": { 11 | "source": { 12 | "type": "registry", 13 | "id": "cordova-plugin-webpack@^1.0.5" 14 | }, 15 | "is_top_level": true, 16 | "variables": {} 17 | } 18 | } -------------------------------------------------------------------------------- /plugins/android.json: -------------------------------------------------------------------------------- 1 | { 2 | "prepare_queue": { 3 | "installed": [], 4 | "uninstalled": [] 5 | }, 6 | "config_munge": { 7 | "files": {} 8 | }, 9 | "installed_plugins": { 10 | "cordova-plugin-webpack": { 11 | "PACKAGE_NAME": "com.cordova.HalogenNextjs" 12 | }, 13 | "cordova-plugin-whitelist": { 14 | "PACKAGE_NAME": "com.cordova.HalogenNextjs" 15 | } 16 | }, 17 | "dependent_plugins": {} 18 | } 19 | -------------------------------------------------------------------------------- /nix/pkgs/arion/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, fetchFromGitHub, readRevision, ... }: 2 | 3 | # let 4 | # src = fetchFromGitHub ( 5 | # readRevision ./revision.json 6 | # ); 7 | 8 | # # src = /home/srghma/projects/arion; 9 | 10 | # arion = pkgs.callPackage "${src}/arion.nix" {}; 11 | # # arion = (import src {}).arion; 12 | # in 13 | # arion 14 | 15 | (import (builtins.fetchTarball https://github.com/hercules-ci/arion/tarball/master) {}).arion 16 | -------------------------------------------------------------------------------- /packages/worker/Worker/EmailUI.purs: -------------------------------------------------------------------------------- 1 | module Worker.EmailUI where 2 | 3 | import Protolude 4 | import Halogen.HTML as HH 5 | import Halogen.HTML.Properties as HP 6 | 7 | -- XXX: note that
and
result in warning "... are not registered" 8 | 9 | link_to_styled text url = 10 | HH.a 11 | [ HP.style "color: #f57a12; text-decoration: none; font-style: oblique; font-weight: bold;" 12 | , HP.href url 13 | ] 14 | [ HH.text text 15 | ] 16 | 17 | -------------------------------------------------------------------------------- /nix/pkgs/docker-volume-rm-if-exists/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, makeWrapper, docker }: 2 | 3 | stdenv.mkDerivation rec { 4 | name = "docker-volume-rm-if-exists"; 5 | 6 | nativeBuildInputs = [ makeWrapper ]; 7 | 8 | phases = [ "installPhase" ]; 9 | 10 | installPhase = '' 11 | mkdir -p $out/bin 12 | 13 | makeWrapper ${./docker-volume-rm-if-exists} $out/bin/${name} \ 14 | --prefix PATH : ${lib.makeBinPath [ docker ]} 15 | ''; 16 | } 17 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Blocks/PurescriptLogo.svg: -------------------------------------------------------------------------------- 1 | PureScript icon 2 | -------------------------------------------------------------------------------- /packages/src/OptparseExtra.purs: -------------------------------------------------------------------------------- 1 | module OptparseExtra where 2 | 3 | import Options.Applicative 4 | import Protolude 5 | 6 | import Data.String.NonEmpty (NonEmptyString) 7 | import Data.String.NonEmpty as NonEmptyString 8 | 9 | nonEmptyString :: ReadM NonEmptyString 10 | nonEmptyString = eitherReader $ NonEmptyString.fromString >>> note "Expected non empty string" 11 | 12 | nonempty :: ReadM String 13 | nonempty = nonEmptyString <#> NonEmptyString.toString 14 | -------------------------------------------------------------------------------- /config/public/dhparams.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAnhhDKZIyg84On8G29O9aURmFohj2x5PNignaKoSXJuRu3+kvZNhs 3 | 4JpwCXrNndlgXiSpqa5dkYALUn7M8mxsiBlhLp3z6ZeFvbwaSBdDBQst7M9X21Ko 4 | 0swPuaakLbx+85WgLgLJxYWh5KmSw8P22x6JPTZQ2Ss3B/QJVTnJey1GDwkar1uI 5 | 7mF1tGTapCwn3ximpm/Xq164pv7dMEt5T/MEHQIZY6/Slzt+9aNkhYwacoy285fe 6 | nI04IIYTltgT/mPtfL7ckcM4v6Jtitp6f9e7XOUwkbCD60/4Eb7E7EUl7xq62+ee 7 | wbAGr7LgMDPOhxsP9OK9TinE4VN8xMnewwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /docs/inspiration.md: -------------------------------------------------------------------------------- 1 | - https://github.com/bouzuya/tamaru/blob/master/src/Bouzuya/Halogen/StringRenderer.purs 2 | - https://github.com/bouzuya/fwt/blob/648b2e8b2bdf4e52718c108ba2e8c113c2faac4d/src/View.purs#L114 3 | - https://github.com/natefaubion/slamdata/blob/8d7534b0a8e9819b2719ca088c620cf098630c80/src/SlamData/Workspace/Card/Setups/Geo/Heatmap/Eval.purs#L158 4 | - https://github.com/nwolverson/purescript-weekchart/blob/fbc6223ff9f80ba6bc936c923b38ee1b11d53af3/src/Main.purs#L63 5 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Navigate.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Navigate where 2 | 3 | import Protolude 4 | 5 | import Control.Monad.Reader (asks) 6 | 7 | import NextjsApp.Route as NextjsApp.Route 8 | 9 | navigate :: 10 | forall r m routes. 11 | MonadEffect m => 12 | MonadAsk { navigate :: Variant routes -> Effect Unit | r } m => 13 | Variant routes -> 14 | m Unit 15 | navigate route = do 16 | navigate' <- asks _.navigate 17 | liftEffect $ navigate' route 18 | -------------------------------------------------------------------------------- /migrations/0000000003-posts/01_posts/03_policies.up.sql: -------------------------------------------------------------------------------- 1 | alter table app_public.posts enable row level security; 2 | 3 | create policy select_posts on app_public.posts for select using (true); 4 | create policy update_posts on app_public.posts for update using (user_id = app_public.current_user_id_or_null()); 5 | create policy delete_posts on app_public.posts for delete using (user_id = app_public.current_user_id_or_null()); 6 | 7 | grant select on table app_public.posts to app_user; 8 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lifecycle/README.md: -------------------------------------------------------------------------------- 1 | # Lifecycles 2 | 3 | This example demonstrates component lifecycles. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-lifecycle 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/lifecycle/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /spago-api-server.dhall: -------------------------------------------------------------------------------- 1 | { name = "api-server" 2 | , dependencies = ./dependencies.dhall 3 | , packages = ./packages.dhall 4 | , sources = 5 | [ "./packages/src/**/*.purs" 6 | , "./packages/api-server/**/*.purs" 7 | , "./packages/api-server-exceptions/**/*.purs" 8 | , "./packages/api-server-config/**/*.purs" 9 | , "./packages/client/NextjsApp/AppM.purs" 10 | , "./packages/client/NextjsApp/Route.purs" 11 | , "./packages/client/NextjsApp/Link/Types.purs" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /migrations/0000000001-start/03_current_user_id_required.up.sql: -------------------------------------------------------------------------------- 1 | create or replace function app_public.current_user_id_required() returns uuid as $function$ 2 | select current_setting('jwt.claims.user_id', false)::uuid; 3 | $function$ LANGUAGE sql stable; 4 | 5 | comment on function app_public.current_user_id_required() is 6 | '@omit 7 | Get current user id or raise error that returns 403 status code'; 8 | 9 | grant execute on function app_public.current_user_id_required() to public; 10 | -------------------------------------------------------------------------------- /packages/src/Webpack/Plugins.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack") 2 | 3 | exports.webpack = { 4 | _DefinePlugin: function(opts) { 5 | return new webpack.DefinePlugin(opts) 6 | }, 7 | _ProvidePlugin: function(opts) { 8 | return new webpack.ProvidePlugin(opts) 9 | }, 10 | _NoEmitOnErrorsPlugin: new webpack.NoEmitOnErrorsPlugin(), 11 | optimize: { 12 | _LimitChunkCountPlugin: function(opts) { return new webpack.optimize.LimitChunkCountPlugin(opts) } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spago-feature-tests.dhall: -------------------------------------------------------------------------------- 1 | { name = "feature-tests" 2 | , dependencies = ./dependencies.dhall 3 | , packages = ./packages.dhall 4 | , sources = 5 | [ "./packages/src/**/*.purs" 6 | , "./packages/feature-tests/**/*.purs" 7 | , "./packages/worker/**/*.purs" 8 | , "./packages/client/NextjsApp/AppM.purs" 9 | , "./packages/client/NextjsApp/Route.purs" 10 | , "./packages/client/NextjsApp/WebRouteDuplexCodec.purs" 11 | , "./packages/client/NextjsApp/Link/Types.purs" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /nix/pkgs/arion/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | # nix-prefetch-git https://github.com/hercules-ci/arion --rev cc9e70a2ccdacb435c835079b9d473683ac0639b > "$SCRIPT_DIR/revision.json" 6 | nix-prefetch-git https://github.com/hercules-ci/arion > "$SCRIPT_DIR/revision.json" 7 | # nix-prefetch-git https://github.com/srghma/arion --rev 108767c03231cfd9bde6c06df122f08563f77cb2 > "$SCRIPT_DIR/revision.json" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.gcroots/ 2 | /bower_components/ 3 | /node_modules/ 4 | /.pulp-cache/ 5 | /output/ 6 | /generated-docs/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /examples/dist/ 12 | /yarn-error.log 13 | .dist/ 14 | .dist-dev/ 15 | /plugins/cordova-plugin-webpack/ 16 | /plugins/cordova-plugin-whitelist/ 17 | /platforms/android/ 18 | /core 19 | /www/ 20 | /package-lock.json 21 | /config/ignored/* 22 | !/config/ignored/*.example 23 | !/config/ignored/*.setup.sh 24 | .tmp-arion-*.yaml 25 | /.feature-tests 26 | -------------------------------------------------------------------------------- /packages/src/SpecAssertsExtra.purs: -------------------------------------------------------------------------------- 1 | module SpecAssertsExtra where 2 | 3 | import Control.Monad.Error.Class 4 | import Effect.Exception 5 | import Data.String (Pattern(..), contains) 6 | import Prelude 7 | import Test.Spec.Assertions (fail, shouldContain, shouldEqual) 8 | 9 | shouldContainString 10 | :: forall m f 11 | . MonadThrow Error m 12 | => String 13 | -> Pattern 14 | -> m Unit 15 | shouldContainString e c = 16 | unless (contains c e) $ 17 | fail $ (show c) <> " ∉ " <> (show e) 18 | 19 | -------------------------------------------------------------------------------- /nix/pkgs/ntmuxp/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | terraform = pkgs.terraform.withPlugins (p: with p; [ aws tls local ]); 5 | 6 | revData = builtins.fromJSON (builtins.readFile ./revision.json); 7 | 8 | src = builtins.fetchTarball { 9 | inherit (revData) sha256; 10 | url = "${revData.url}/archive/${revData.rev}.tar.gz"; 11 | }; 12 | 13 | # src = ~/projects/ntmuxp; 14 | in 15 | # pkgs.callPackage "${src}/default.nix" { inherit pkgs; } 16 | pkgs.callPackage "${src}/default.nix" { } 17 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/random_enum.sql: -------------------------------------------------------------------------------- 1 | 2 | begin; 3 | 4 | -- call as random_enum(null::app_public.leed_rating_enum) 5 | create or replace function random_enum(relation_name anyelement, out result anyenum) 6 | returns anyenum as $func$ 7 | begin 8 | execute format( 9 | $sql$ 10 | select unnest(enum_range(null::%1$I)) 11 | order by random() 12 | limit 1; 13 | $sql$, 14 | pg_typeof(relation_name) 15 | ) into result; 16 | return; 17 | end; 18 | $func$ language plpgsql; 19 | 20 | commit; 21 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/README.md: -------------------------------------------------------------------------------- 1 | # DeeplyNested 2 | 3 | the structure is the following 4 | 5 | A 6 | B 7 | D 8 | F 9 | E 10 | C 11 | 12 | 13 | ## Building 14 | 15 | You can build this example from the root of the Halogen project: 16 | 17 | ```sh 18 | npm install 19 | npm run example-nested 20 | ``` 21 | 22 | This will bundle a runnable JS file, `example.js`, in the `examples/nested/dist` directory. You can view the running application by opening the corresponding `index.html` file. 23 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Router/Server.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Router.Server where 2 | 3 | import Protolude 4 | import Halogen as H 5 | import NextjsApp.AppM (AppM) 6 | import NextjsApp.Router.Shared (CurrentPageInfo, Query, maybeRenderPage) 7 | 8 | serverComponent :: 9 | forall r. 10 | H.Component Query { currentPageInfo :: Maybe CurrentPageInfo | r } Void AppM 11 | serverComponent = 12 | H.mkComponent 13 | { initialState: identity 14 | , render: maybeRenderPage 15 | , eval: H.mkEval $ H.defaultEval 16 | } 17 | -------------------------------------------------------------------------------- /packages/src/Favicons.js: -------------------------------------------------------------------------------- 1 | const favicons = require('favicons') 2 | 3 | exports._favicons = function(buffer) { 4 | return function(config) { 5 | return function (onError, onSuccess) { 6 | favicons(buffer, config, function (err, res) { 7 | if (err) { 8 | onError(err) 9 | } else { 10 | onSuccess(res) 11 | } 12 | }) 13 | 14 | return function (cancelError, onCancelerError, onCancelerSuccess) { 15 | onCancelerSuccess(); 16 | }; 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /nix/pkgs/dump-schema/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, makeWrapper, postgresql, coreutils }: 2 | 3 | stdenv.mkDerivation rec { 4 | name = "dump-schema"; 5 | 6 | nativeBuildInputs = [ makeWrapper ]; 7 | 8 | phases = [ "installPhase" ]; 9 | 10 | installPhase = '' 11 | mkdir -p $out/bin 12 | 13 | cp ${./dump-schema} $out/dump-schema 14 | 15 | patchShebangs $out/dump-schema 16 | 17 | makeWrapper $out/dump-schema $out/bin/${name} \ 18 | --prefix PATH : ${lib.makeBinPath [ postgresql coreutils ]} 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-aff-ajax/README.md: -------------------------------------------------------------------------------- 1 | # `Aff` and AJAX 2 | 3 | This example demonstrates how to make an AJAX request in a component `eval` function. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-effects-aff-ajax 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/effects-aff-ajax/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/api-server/ApiServer/PostgraphilePassportAuthPlugin/Shared.purs: -------------------------------------------------------------------------------- 1 | module ApiServer.PostgraphilePassportAuthPlugin.Shared where 2 | 3 | import ApiServer.PostgraphilePassportAuthPlugin.Types 4 | 5 | import Protolude 6 | import Effect.Uncurried 7 | import Data.Function.Uncurried 8 | 9 | foreign import appPublicUsersFragment :: FragmentMaker -> SqlFragment 10 | 11 | foreign import mkSelectGraphQLResultFromTable :: 12 | Fn2 13 | String 14 | { fragment :: FragmentMaker, value :: SqlValueFn } 15 | (EffectFn2 String QueryBuilder Unit) 16 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/select/app_user.sql: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_user'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | insert into app_public.users 10 | (id, email, first_name, last_name) 11 | values 12 | (:user_id, random_email(), random_string(), random_string()); 13 | 14 | set local role :role; 15 | select is(current_user, :role); 16 | 17 | select is_empty( 18 | 'select id from app_public.users' 19 | ); 20 | 21 | select finish(); 22 | 23 | rollback; 24 | -------------------------------------------------------------------------------- /packages/src/Webpack/GetError.purs: -------------------------------------------------------------------------------- 1 | module Webpack.GetError where 2 | 3 | import Protolude 4 | import Data.Array as Array 5 | import Data.Array.NonEmpty (NonEmptyArray) 6 | import Data.Array.NonEmpty as NonEmptyArray 7 | import Webpack.Compiler (MultiStats) 8 | 9 | webpackGetErrors :: Maybe Error -> MultiStats -> Maybe (NonEmptyArray Error) 10 | webpackGetErrors = \error stats -> case error of 11 | Just e -> Just $ NonEmptyArray.singleton e 12 | Nothing -> Array.findMap (\stat -> NonEmptyArray.fromArray stat.compilation.errors) stats.stats 13 | -------------------------------------------------------------------------------- /packages/src/Webpack/Plugins.purs: -------------------------------------------------------------------------------- 1 | module Webpack.Plugins where 2 | 3 | import Foreign (Foreign) 4 | import Foreign.Object (Object) 5 | import Webpack.Types (WebpackPluginInstance) 6 | 7 | foreign import webpack :: 8 | { _DefinePlugin :: Object Foreign -> WebpackPluginInstance 9 | , _ProvidePlugin :: forall options. { | options } -> WebpackPluginInstance 10 | , _NoEmitOnErrorsPlugin :: WebpackPluginInstance 11 | , optimize :: 12 | { _LimitChunkCountPlugin :: forall options. { | options } -> WebpackPluginInstance 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/src/LazyLoaded.purs: -------------------------------------------------------------------------------- 1 | module Example.Lazy.LazyLoaded (component) where 2 | 3 | import Protolude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | 8 | component :: forall q i o m. H.Component q i o m 9 | component = 10 | H.mkComponent 11 | { initialState: const unit 12 | , render 13 | , eval: H.mkEval H.defaultEval 14 | } 15 | 16 | render :: forall m query. Unit -> H.ComponentHTML query () m 17 | render _ = 18 | HH.div_ 19 | [ HH.text "I'm lazy loaded child (LOADED)" 20 | ] 21 | -------------------------------------------------------------------------------- /packages/feature-tests/FeatureTests/FeatureTestSpecUtils/EmailAVar.purs: -------------------------------------------------------------------------------- 1 | module FeatureTests.FeatureTestSpecUtils.EmailAVar where 2 | 3 | import Protolude 4 | 5 | import Control.Monad.Reader.Class (asks) 6 | import Effect.AVar (AVar) 7 | import Effect.Aff.AVar as AVar 8 | import Worker.Main as Worker 9 | import Worker.Types as Worker 10 | 11 | waitForEmail 12 | :: forall m r 13 | . MonadAsk { emailAVar :: AVar Worker.Email | r } m 14 | => MonadAff m 15 | => m Worker.Email 16 | waitForEmail = asks _.emailAVar >>= liftAff <<< AVar.take 17 | 18 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/effects-effect-random/README.md: -------------------------------------------------------------------------------- 1 | # `Effect` and Random 2 | 3 | This example demonstrates how to use an `Effect` function in a component `eval` function. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-effects-effect-random 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/effects-effect-random/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | // require('postcss-import'), 4 | // require('postcss-preset-env'), 5 | // require('cssnano')({ preset: 'default' }), 6 | // ...( 7 | // production ? 8 | // { 9 | // '@fullhuman/postcss-purgecss': { 10 | // content: ['./client/**/*.js'], 11 | // defaultExtractor: (content) => content.match(/[\w-/:]+(? Array Rule 18 | -------------------------------------------------------------------------------- /docker/common/pgadmin.nix: -------------------------------------------------------------------------------- 1 | {}: 2 | 3 | # -- user - pgadmin4@pgadmin.org 4 | # -- user pass - admin 5 | # -- hostname - postgres (yes, pgadmin4 client reaches postgres through pgadmin4 server, you dont need to expose $POSTGRES_PORT port) 6 | # -- username - $POSTGRES_USER 7 | # -- password - $POSTGRES_PASSWORD 8 | 9 | { 10 | image = "dpage/pgadmin4:latest"; 11 | ports = [ 12 | "5050:80" 13 | ]; 14 | depends_on = [ 15 | "postgres" 16 | ]; 17 | 18 | environment = { 19 | PGADMIN_DEFAULT_EMAIL = "foo"; 20 | PGADMIN_DEFAULT_PASSWORD = "foo"; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/ace/README.md: -------------------------------------------------------------------------------- 1 | # Ace Editor 2 | 3 | This example demonstrates integrating a third-party component. It uses queries to control behavior in Ace editor and output messages to observe changes within the editor. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-ace 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/ace/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-multitype/README.md: -------------------------------------------------------------------------------- 1 | # Component With Multiple Child Component Types 2 | 3 | This example demonstrates a component which has child components of differing types. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-components-multitype 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/components-multitype/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/src/Webpack/FFI.js: -------------------------------------------------------------------------------- 1 | exports.webpackEntrypontName = function(entrypoint) { return entrypoint.name } 2 | 3 | exports.webpackEntrypontGetFiles = function(entrypoint) { return entrypoint.getFiles() } 4 | 5 | exports._rawSource = function(string) { 6 | return new (require('webpack-sources').RawSource)(string) 7 | } 8 | 9 | exports.setAsset = function(assets, name, rawSource) { assets[name] = rawSource } 10 | 11 | exports.compilationGetEntrypoints = function(compilation) { 12 | // console.log('compilation', compilation.entrypoints) 13 | return compilation.entrypoints 14 | } 15 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | This example demonstrates a button component embedded in a parent component. The parent component can send queries to and receive output messages from the button. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-components 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/components/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/keyboard-input/README.md: -------------------------------------------------------------------------------- 1 | # Keyboard Input (Event Sources) 2 | 3 | This example demonstrates how to selectively capture keyboard events and, more generally, how to use `EventSource`s in Halogen. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-keyboard-input 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/keyboard-input/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /dev-commands/all-commands.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | 3 | let 4 | 5 | config = rec { 6 | # type Name = String 7 | # type Code = String 8 | # Map Name Code -> List Pkg 9 | mkScripts = attrsetOfNamesAndCode: pkgs.lib.attrValues (pkgs.lib.mapAttrs (name: code: pkgs.writeShellScriptBin name code) attrsetOfNamesAndCode); 10 | 11 | mkCommand = environment: content: '' 12 | set -eux 13 | 14 | ${pkgs.mylib.exportEnvsCommand environment} 15 | 16 | ${content} 17 | ''; 18 | }; 19 | 20 | in 21 | 22 | builtins.concatLists [ 23 | (import ./dev/default.nix { inherit pkgs config; }) 24 | ] 25 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailWeb/Types.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.PageImplementations.VerifyUserEmailWeb.Types where 2 | 3 | import Protolude 4 | 5 | import Halogen as H 6 | 7 | type Query 8 | = Const Void 9 | 10 | type Input 11 | = Unit 12 | 13 | type Message 14 | = Void 15 | 16 | data Action 17 | = Action__Initialize 18 | 19 | type ChildSlots = () 20 | 21 | data VerifyUserEmailError 22 | = VerifyUserEmailError__TokenExpired 23 | | VerifyUserEmailError__Unknown String 24 | 25 | type State = 26 | { loginError :: Maybe VerifyUserEmailError 27 | } 28 | -------------------------------------------------------------------------------- /migrations/0000000001-start/02_current_user_id_or_null.up.sql: -------------------------------------------------------------------------------- 1 | -- TODO: should be in app_hidden? 2 | 3 | create or replace function app_public.current_user_id_or_null() returns uuid as $function$ 4 | select nullif(current_setting('jwt.claims.user_id', true), '')::uuid; 5 | $function$ LANGUAGE sql stable; 6 | 7 | comment on function app_public.current_user_id_or_null() is 8 | '@omit 9 | Handy method to get the current user ID for use in RLS policies, etc; in GraphQL, use `query { currentUser { id } }` instead.'; 10 | 11 | grant execute on function app_public.current_user_id_or_null() to public; 12 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/VerifyUserEmailMobile/Types.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.PageImplementations.VerifyUserEmailMobile.Types where 2 | 3 | import Protolude 4 | 5 | import Halogen as H 6 | 7 | type Query 8 | = Const Void 9 | 10 | type Input 11 | = Unit 12 | 13 | type Message 14 | = Void 15 | 16 | data Action 17 | = Action__Initialize 18 | 19 | type ChildSlots = () 20 | 21 | data VerifyUserEmailError 22 | = VerifyUserEmailError__TokenExpired 23 | | VerifyUserEmailError__Unknown String 24 | 25 | type State = 26 | { loginError :: Maybe VerifyUserEmailError 27 | } 28 | -------------------------------------------------------------------------------- /packages/db-tests/tests/roles/app_anonymous.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_anonymous'$$ 2 | 3 | begin; 4 | 5 | select no_plan(); 6 | 7 | select database_privs_are( 8 | current_database(), 9 | :role, 10 | array['CONNECT', 'TEMPORARY']::text[] 11 | ); 12 | 13 | select schema_privs_are( 14 | 'public', 15 | :role, 16 | array['USAGE', 'CREATE']::text[] 17 | ); 18 | 19 | select schema_privs_are( 20 | 'app_public', 21 | :role, 22 | array['USAGE']::text[] 23 | ); 24 | 25 | select schema_privs_are( 26 | 'app_private', 27 | :role, 28 | array[]::text[] 29 | ); 30 | 31 | select finish(); 32 | 33 | rollback; 34 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/src/LazyLoadedImport.purs: -------------------------------------------------------------------------------- 1 | module Example.Lazy.LazyLoadedImport where 2 | 3 | import Protolude 4 | 5 | import Control.Promise (Promise) 6 | import Control.Promise as Promise 7 | import Data.Time.Duration (Milliseconds(..)) 8 | import Effect.Aff (delay) 9 | import Halogen as H 10 | 11 | foreign import lazyLoadedImport_ :: forall q i o m . Effect (Promise { component :: H.Component q i o m }) 12 | 13 | lazyLoadedImport :: forall q i o m . Aff (H.Component q i o m) 14 | lazyLoadedImport = do 15 | delay (Milliseconds 3000.0) 16 | Promise.toAffE lazyLoadedImport_ <#> _.component 17 | -------------------------------------------------------------------------------- /packages/db-tests/tests/roles/app_user.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_user'$$ 2 | 3 | begin; 4 | 5 | select no_plan(); 6 | 7 | select database_privs_are( 8 | current_database(), 9 | :role, 10 | array['CONNECT', 'TEMPORARY']::text[] 11 | ); 12 | 13 | -- TODO: should create? 14 | select schema_privs_are( 15 | 'public', 16 | :role, 17 | array['USAGE', 'CREATE'] 18 | ); 19 | 20 | select schema_privs_are( 21 | 'app_public', 22 | :role, 23 | array['USAGE'] 24 | ); 25 | 26 | select schema_privs_are( 27 | 'app_private', 28 | :role, 29 | array[]::text[] 30 | ); 31 | 32 | select finish(); 33 | 34 | rollback; 35 | -------------------------------------------------------------------------------- /migrations/0000000001-start/05_generate_url_safe_token.up.sql: -------------------------------------------------------------------------------- 1 | -- From https://stackoverflow.com/a/1374794/3574379 2 | -- base64 strings can contains the "+", "=" and "/" which are unsafe 3 | -- remove these characters 4 | 5 | create or replace function app_public.generate_url_safe_token() returns text as $function$ 6 | select translate( 7 | encode(gen_random_bytes(8), 'base64'), 8 | '+/=', 9 | '' 10 | ) 11 | $function$ LANGUAGE sql stable; 12 | 13 | comment on function app_public.generate_url_safe_token() is 14 | '@omit'; 15 | 16 | grant execute on function app_public.generate_url_safe_token() to public; 17 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/C.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.C (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | 8 | type State = Unit 9 | 10 | data Action = Void 11 | 12 | type ChildSlots = () 13 | 14 | component :: forall q i o m. H.Component q i o m 15 | component = 16 | H.mkComponent 17 | { initialState: const unit 18 | , render 19 | , eval: H.mkEval H.defaultEval 20 | } 21 | 22 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 23 | render state = 24 | HH.ul_ 25 | [ HH.li_ [ HH.text "c" ] 26 | ] 27 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/E.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.E (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | 8 | type State = Unit 9 | 10 | data Action = Void 11 | 12 | type ChildSlots = () 13 | 14 | component :: forall q i o m. H.Component q i o m 15 | component = 16 | H.mkComponent 17 | { initialState: const unit 18 | , render 19 | , eval: H.mkEval H.defaultEval 20 | } 21 | 22 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 23 | render state = 24 | HH.ul_ 25 | [ HH.li_ [ HH.text "e" ] 26 | ] 27 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/F.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.F (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | 8 | type State = Unit 9 | 10 | data Action = Void 11 | 12 | type ChildSlots = () 13 | 14 | component :: forall q i o m. H.Component q i o m 15 | component = 16 | H.mkComponent 17 | { initialState: const unit 18 | , render 19 | , eval: H.mkEval H.defaultEval 20 | } 21 | 22 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 23 | render state = 24 | HH.ul_ 25 | [ HH.li_ [ HH.text "f" ] 26 | ] 27 | -------------------------------------------------------------------------------- /migrations/0000000001-start.sql: -------------------------------------------------------------------------------- 1 | -- generated by ./gen-migrations.hs 2 | 3 | -- ==== UP ==== 4 | 5 | begin; 6 | 7 | \include 0000000001-start/01_prelude.up.sql 8 | \include 0000000001-start/02_current_user_id_or_null.up.sql 9 | \include 0000000001-start/03_current_user_id_required.up.sql 10 | \include 0000000001-start/04_tg__set_updated_at.up.sql 11 | \include 0000000001-start/05_generate_url_safe_token.up.sql 12 | \include 0000000001-start/06_is_between_0_and_100.up.sql 13 | \include 0000000001-start/07_implication.up.sql 14 | 15 | 16 | commit; 17 | 18 | -- ==== DOWN ==== 19 | 20 | -- do nothing, this should never happen 21 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/select/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | insert into app_public.users 10 | (id, email, first_name, last_name) 11 | values 12 | (:user_id, random_email(), random_string(), random_string()); 13 | 14 | set local role :role; 15 | select is(current_user, :role); 16 | 17 | prepare expected as values 18 | (:user_id :: uuid); 19 | 20 | select set_eq( 21 | 'select id from app_public.users', 22 | 'expected' 23 | ); 24 | 25 | select finish(); 26 | 27 | rollback; 28 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/confirm/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set func $$'confirm'$$ 4 | \set args $$'{"text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/db-tests/tests/queries/current_user/priviliges.sql: -------------------------------------------------------------------------------- 1 | \set schema $$'app_public'$$ 2 | \set func $$'current_user'$$ 3 | \set args $$'{}'$$::text[] 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | SELECT function_privs_are( 10 | :schema, 11 | :func, 12 | :args, 13 | 'app_owner', 14 | '{"EXECUTE"}' 15 | ); 16 | 17 | SELECT function_privs_are( 18 | :schema, 19 | :func, 20 | :args, 21 | 'app_user', 22 | '{"EXECUTE"}' 23 | ); 24 | 25 | SELECT function_privs_are( 26 | :schema, 27 | :func, 28 | :args, 29 | 'app_anonymous', 30 | '{"EXECUTE"}' 31 | ); 32 | 33 | select finish(); 34 | 35 | rollback; 36 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic 2 | 3 | This example demonstrates close to the smallest Halogen component you can build. It's unlikely you'd make components this small in a real world application, but this lets you see the essential parts of a component definition. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-basic 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/basic/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/db-tests/extensions/randomly_set_empty_jwt_user_id.sql_: -------------------------------------------------------------------------------- 1 | create or replace function randomly_set_empty_jwt_user_id() returns void as $function$ 2 | declare 3 | /* rand integer := random_between(0, 3); */ 4 | rand integer := 1; 5 | begin 6 | case 7 | when rand = 0 then 8 | null; 9 | when rand = 1 then 10 | set local jwt.claims.user_id to default; 11 | when rand = 2 then 12 | set local jwt.claims.user_id to ''; 13 | when rand = 3 then 14 | set local jwt.claims.user_id to random_string(); 15 | end case; 16 | end; 17 | $function$ LANGUAGE plpgsql; 18 | 19 | select randomly_set_empty_jwt_user_id(); 20 | -------------------------------------------------------------------------------- /packages/feature-tests/FeatureTests/FeatureTestSpecUtils/LunaparkUtils.purs: -------------------------------------------------------------------------------- 1 | module FeatureTests.FeatureTestSpecUtils.LunaparkUtils where 2 | 3 | import Protolude 4 | import FeatureTests.FeatureTestSpecUtils.AffRetry 5 | import FeatureTests.FeatureTestSpecUtils.Lunapark (runLunapark) 6 | import Lunapark as Lunapark 7 | import Test.Spec.Assertions (shouldEqual) 8 | 9 | waitForInputValueToEqual location expected = 10 | retryAction $ 11 | ( ( runLunapark $ 12 | Lunapark.findElement location 13 | >>= \e -> Lunapark.getAttribute e "value" 14 | ) 15 | >>= \text -> text `shouldEqual` expected 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /docker/common/db_tests.nix: -------------------------------------------------------------------------------- 1 | { pkgs, rootProjectDir }: 2 | { 3 | useHostStore = true; 4 | 5 | volumes = [ 6 | "${rootProjectDir}/db_tests:/db_tests:ro" 7 | "/tmp" # for shmig 8 | ]; 9 | 10 | working_dir = "/db_tests/tests"; 11 | 12 | environment = rec { 13 | POSTGRES_USER = "app_admin"; 14 | POSTGRES_PASSWORD = "app_admin_pass"; 15 | PGPASSWORD = POSTGRES_PASSWORD; # for db-tests-prepare (for psql) 16 | POSTGRES_HOST = "postgres"; 17 | POSTGRES_PORT = 5432; 18 | POSTGRES_DB = "nextjsdemo_test"; 19 | }; 20 | 21 | depends_on = [ 22 | "postgres" 23 | ]; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-inputs/README.md: -------------------------------------------------------------------------------- 1 | # Components With Input 2 | 3 | This example demonstrates a component which receives input from a parent component. When the parent re-renders the child component will receive a new input value and update accordingly. 4 | 5 | ## Building 6 | 7 | You can build this example from the root of the Halogen project: 8 | 9 | ```sh 10 | npm install 11 | npm run example-components-inputs 12 | ``` 13 | 14 | This will bundle a runnable JS file, `example.js`, in the `examples/components-inputs/dist` directory. You can view the running application by opening the corresponding `index.html` file. 15 | -------------------------------------------------------------------------------- /packages/db-tests/tests/roles/app_admin.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_admin'$$ 2 | 3 | begin; 4 | 5 | select no_plan(); 6 | 7 | select database_privs_are( 8 | current_database(), 9 | :role, 10 | array['CONNECT', 'TEMPORARY', 'CREATE'] 11 | ); 12 | 13 | -- TODO: should create? 14 | select schema_privs_are( 15 | 'public', 16 | :role, 17 | array['USAGE', 'CREATE'] 18 | ); 19 | 20 | select schema_privs_are( 21 | 'app_public', 22 | :role, 23 | array['USAGE', 'CREATE'] 24 | ); 25 | 26 | select schema_privs_are( 27 | 'app_private', 28 | :role, 29 | array['USAGE', 'CREATE'] 30 | ); 31 | 32 | select finish(); 33 | 34 | rollback; 35 | -------------------------------------------------------------------------------- /packages/db-tests/tests/roles/app_owner.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_owner'$$ 2 | 3 | begin; 4 | 5 | select no_plan(); 6 | 7 | select database_privs_are( 8 | current_database(), 9 | :role, 10 | array['CONNECT', 'TEMPORARY', 'CREATE'] 11 | ); 12 | 13 | -- TODO: should create? 14 | select schema_privs_are( 15 | 'public', 16 | :role, 17 | array['USAGE', 'CREATE'] 18 | ); 19 | 20 | select schema_privs_are( 21 | 'app_public', 22 | :role, 23 | array['USAGE', 'CREATE'] 24 | ); 25 | 26 | select schema_privs_are( 27 | 'app_private', 28 | :role, 29 | array['USAGE', 'CREATE'] 30 | ); 31 | 32 | select finish(); 33 | 34 | rollback; 35 | -------------------------------------------------------------------------------- /packages/src/ContribWebpackPlugins.purs: -------------------------------------------------------------------------------- 1 | module ContribWebpackPlugins where 2 | 3 | import Webpack.Types (WebpackPluginInstance) 4 | 5 | foreign import _MiniCssExtractPlugin :: forall options. { | options } -> WebpackPluginInstance 6 | 7 | foreign import _CleanWebpackPlugin :: WebpackPluginInstance 8 | 9 | foreign import _BundleAnalyzerPlugin :: forall options. { | options } -> WebpackPluginInstance 10 | 11 | foreign import _HtmlWebpackPlugin :: forall options. { | options } -> WebpackPluginInstance 12 | 13 | data HtmlWebpackPlugin__Tags 14 | 15 | foreign import htmlWebpackPlugin__tags__toString :: HtmlWebpackPlugin__Tags -> Array String 16 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/reset_password/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set func $$'reset_password'$$ 4 | \set args $$'{"text","text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/src/SimpleLookupEnv.purs: -------------------------------------------------------------------------------- 1 | module SimpleLookupEnv where 2 | 3 | import Prelude 4 | import Data.Maybe (maybe) 5 | import Data.Int as Data.Int 6 | import Effect.Exception (throw) 7 | import Node.Process (lookupEnv) 8 | import Effect (Effect) 9 | 10 | lookupEnvValue :: String -> Effect String 11 | lookupEnvValue name = lookupEnv name >>= maybe (throw $ "Missing environment variable " <> name) pure 12 | 13 | lookupEnvValueInt :: String -> Effect Int 14 | lookupEnvValueInt name = lookupEnvValue name >>= (\string -> (pure $ Data.Int.fromString string) >>= maybe (throw $ "Invalid " <> name <> ": expected valid integer but got " <> string) pure) 15 | -------------------------------------------------------------------------------- /migrations/0000000002-users/16_app_private.tables.user_sessions.up.sql: -------------------------------------------------------------------------------- 1 | -- from https://github.com/voxpelli/node-connect-pg-simple/blob/master/table.sql 2 | 3 | -- based on guide from https://www.npmjs.com/package/express-pg-session 4 | 5 | CREATE TABLE app_private.user_sessions ( 6 | "sid" varchar NOT NULL COLLATE "default", 7 | "sess" json NOT NULL, 8 | "expire" timestamp(6) NOT NULL 9 | ) 10 | WITH (OIDS=FALSE); 11 | 12 | ALTER TABLE app_private.user_sessions ADD CONSTRAINT "app_private_user_sessions_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE; 13 | 14 | CREATE INDEX "IDX_session_expire" ON app_private.user_sessions ("expire"); 15 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/register/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set func $$'register'$$ 4 | \set args $$'{"text", "text", "text", "text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/resend_confirmation/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set func $$'resend_confirmation'$$ 4 | \set args $$'{"text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/send_reset_password/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set func $$'send_reset_password'$$ 4 | \set args $$'{"text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /nix/nixpkgs/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix curl jq 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | 6 | owner="nixos" 7 | repo="nixpkgs-channels" 8 | rev="nixos-unstable" 9 | 10 | full_rev=$(curl --silent https://api.github.com/repos/$owner/$repo/git/refs/heads/$rev | jq -r .object.sha) 11 | 12 | echo "full_rev=$full_rev" 13 | 14 | expected_sha=$(nix-prefetch-url https://github.com/$owner/$repo/archive/$full_rev.tar.gz) 15 | 16 | cat >"$SCRIPT_DIR/revision.json" < SelectionSet 13 | Scope__WebLoginPayload 14 | r 15 | user = selectionForCompositeField 16 | "user" 17 | [] 18 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 19 | -------------------------------------------------------------------------------- /packages/db-tests/tests/queries/user_by_username_or_verified_email/priviliges.sql: -------------------------------------------------------------------------------- 1 | \set schema $$'app_public'$$ 2 | \set func $$'user_by_username_or_verified_email'$$ 3 | \set args $$'{text}'$$::text[] 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | SELECT function_privs_are( 10 | :schema, 11 | :func, 12 | :args, 13 | 'app_owner', 14 | '{"EXECUTE"}' 15 | ); 16 | 17 | SELECT function_privs_are( 18 | :schema, 19 | :func, 20 | :args, 21 | 'app_user', 22 | '{"EXECUTE"}' 23 | ); 24 | 25 | SELECT function_privs_are( 26 | :schema, 27 | :func, 28 | :args, 29 | 'app_anonymous', 30 | '{"EXECUTE"}' 31 | ); 32 | 33 | select finish(); 34 | 35 | rollback; 36 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Object/WebRegisterPayload.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Object.WebRegisterPayload where 2 | 3 | import GraphQLClient 4 | ( SelectionSet 5 | , selectionForCompositeField 6 | , graphqlDefaultResponseFunctorOrScalarDecoderTransformer 7 | ) 8 | import NextjsGraphqlApi.Scopes (Scope__User, Scope__WebRegisterPayload) 9 | 10 | user :: forall r . SelectionSet 11 | Scope__User 12 | r -> SelectionSet 13 | Scope__WebRegisterPayload 14 | r 15 | user = selectionForCompositeField 16 | "user" 17 | [] 18 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 19 | -------------------------------------------------------------------------------- /packages/src/RunExtra.purs: -------------------------------------------------------------------------------- 1 | module RunExtra where 2 | 3 | import Protolude 4 | 5 | import Data.Array (uncons) 6 | import Effect.Aff (delay) 7 | import Prim.Row (class Cons) as Row 8 | import Run (Run, AFF) 9 | import Run as Run 10 | import Run.Except (EXCEPT) 11 | import Run.Except as Run 12 | import Unsafe.Coerce (unsafeCoerce) 13 | 14 | -- from https://github.com/purescript-webrow/webrow/blob/68144f421b6652b93bb9ceeb9ed69762286ae905/src/WebRow/PostgreSQL/PG.purs#L135 15 | -- 16 | -- | Run.expand definition is based on `Union` constraint 17 | -- | We want to use Row.Cons here instead 18 | expand' ∷ ∀ l b t t_. Row.Cons l b t_ t ⇒ SProxy l → Run t_ ~> Run t 19 | expand' _ = unsafeCoerce 20 | 21 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Login/Css.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | 6 | max-width: 30%; 7 | min-width: 400px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | 12 | .logo { 13 | max-width: 45%; 14 | min-width: 200px; 15 | margin-left: auto; 16 | margin-right: auto; 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | flex-direction: row; 22 | justify-content: flex-end; 23 | } 24 | 25 | .buttons__button { 26 | margin-left: 10px; 27 | } 28 | 29 | .input { 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: center; 33 | padding-bottom: 10px; 34 | } 35 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Register/Css.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | 6 | max-width: 30%; 7 | min-width: 400px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | 12 | .logo { 13 | max-width: 45%; 14 | min-width: 200px; 15 | margin-left: auto; 16 | margin-right: auto; 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | flex-direction: row; 22 | justify-content: flex-end; 23 | } 24 | 25 | .buttons__button { 26 | margin-left: 10px; 27 | } 28 | 29 | .input { 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: center; 33 | padding-bottom: 10px; 34 | } 35 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/login_or_register_oauth/priviliges.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_private'$$ 3 | \set func $$'login_or_register_oauth'$$ 4 | \set args $$'{ "text", "text", "text", "text", "text", "text" }'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_owner', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_user', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_owner', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/feature-tests/FeatureTests/Tests/Login/ErrorNotConfirmedSpec.purs_: -------------------------------------------------------------------------------- 1 | module FeatureTests.Tests.Login.ErrorNotConfirmedSpec where 2 | 3 | import Prelude 4 | import Control.Monad.Error.Class 5 | import Control.Monad.Reader.Trans 6 | import Data.Maybe 7 | import Data.Identity 8 | import Data.Time.Duration 9 | import Effect 10 | import Effect.Aff 11 | import Effect.Class 12 | import Effect.Console 13 | import Effect.Exception 14 | import Selenium.Browser 15 | import Selenium.Builder 16 | import Selenium.Monad 17 | import Selenium.Types 18 | import Test.Spec 19 | import Unsafe.Coerce 20 | import BuildSeleniumChromeDriver 21 | import FeatureTest 22 | 23 | spec :: FeatureTestSpec Unit 24 | spec = pure unit 25 | -------------------------------------------------------------------------------- /nix/pkgs/readRevision/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | 3 | with pkgs; 4 | with lib; 5 | 6 | # EXAMPLE: 7 | # update command example: 8 | # nix-prefetch-git https://github.com/srghma/dunsted-volume > $DOTFILES/nixos/root/prefetched-git-revisions/dunsted-volume.json 9 | 10 | # test match like `nix-instantiate --eval -E 'builtins.match "https?://.*/(.*)/(.*)" "https://github.com/srghma/dunsted-volume"'` 11 | path: 12 | 13 | let 14 | revData = builtins.fromJSON (builtins.readFile path); 15 | url = revData.url; 16 | 17 | m = builtins.match "https?://.*/(.*)/(.*)" url; 18 | owner = builtins.elemAt m 0; 19 | repo = builtins.elemAt m 1; 20 | in { inherit owner repo; inherit (revData) rev sha256; } 21 | -------------------------------------------------------------------------------- /nix/pkgs/pg_prove/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv, postgresql, perlPackages, makeWrapper, lib }: 2 | 3 | # pgql is required to be in $PATH (https://github.com/NixOS/nixpkgs/issues/55663) 4 | stdenv.mkDerivation rec { 5 | name = "pg_prove"; 6 | 7 | nativeBuildInputs = [ makeWrapper ]; 8 | 9 | phases = [ "installPhase" ]; 10 | 11 | installPhase = '' 12 | mkdir -p $out/bin 13 | 14 | makeWrapper ${perlPackages.TAPParserSourceHandlerpgTAP}/bin/pg_prove $out/bin/pg_prove \ 15 | --prefix PATH : ${lib.makeBinPath [ postgresql ]} 16 | 17 | makeWrapper ${perlPackages.TAPParserSourceHandlerpgTAP}/bin/pg_tapgen $out/bin/pg_tapgen \ 18 | --prefix PATH : ${lib.makeBinPath [ postgresql ]} 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/posts/priviligies/app_user.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ :: name 3 | \set table $$'posts'$$ :: name 4 | \set role $$'app_user'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY['SELECT']::text[] 15 | ); 16 | 17 | SELECT any_column_privs_are( 18 | :schema, 19 | :table, 20 | :role, 21 | ARRAY['SELECT']::text[] 22 | ); 23 | 24 | SELECT column_privs_are(:schema, :table, col, :role, ARRAY['SELECT']::text[]) 25 | FROM ( 26 | VALUES('id'), ('name'), ('user_id'), ('content'), ('created_at'), ('updated_at') 27 | ) AS tableAlias (col); 28 | 29 | 30 | select finish(); 31 | 32 | rollback; 33 | -------------------------------------------------------------------------------- /regenerate-purs-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | ################################### 6 | 7 | generate-halogen-css-modules \ 8 | -d ./packages/client 9 | 10 | ################################### 11 | 12 | # sd --flags c "^import ([\w\.]+) (\(.+\) )as ([\w\.]+)" 'import $1 as $3' $(fd --type f ".purs" ./) 13 | 14 | update-module-name-purs \ 15 | -d ./packages/api-server \ 16 | -d ./packages/api-server-exceptions \ 17 | -d ./packages/api-server-config \ 18 | -d ./packages/client \ 19 | -d ./packages/client-tests \ 20 | -d ./packages/client-webpack \ 21 | -d ./packages/db-tests \ 22 | -d ./packages/feature-tests \ 23 | -d ./packages/src \ 24 | -d ./packages/worker 25 | -------------------------------------------------------------------------------- /docker/common/server.nix: -------------------------------------------------------------------------------- 1 | { pkgs, rootProjectDir, rootYarnModules }: 2 | 3 | { 4 | useHostStore = true; 5 | 6 | environment = rec { 7 | POSTGRES_USER = "app_admin"; 8 | POSTGRES_PASSWORD = "app_admin_pass"; 9 | POSTGRES_HOST = "postgres"; 10 | POSTGRES_PORT = 5432; 11 | POSTGRES_DB = "nextjsdemo_test"; 12 | 13 | PORT = 5000; 14 | HOST = "0.0.0.0"; 15 | EXPOSED_SCHEMA = "app_public"; 16 | NODE_ENV = "development"; 17 | DATABASE_URL = "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${toString POSTGRES_PORT}/${POSTGRES_DB}"; 18 | JWT_SECRET = "change_me"; 19 | CORS_ALLOW_ORIGIN = "*"; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Navigate/Client.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Navigate.Client where 2 | 3 | import Protolude 4 | 5 | import Foreign as Foreign 6 | 7 | import NextjsApp.Route as NextjsApp.Route 8 | import NextjsApp.WebRouteDuplexCodec as NextjsApp.WebRouteDuplexCodec 9 | import Routing.Duplex as Routing.Duplex 10 | import Routing.PushState as Routing.PushState 11 | 12 | navigate :: Routing.PushState.PushStateInterface -> (Variant NextjsApp.Route.WebRoutesWithParamRow) -> Effect Unit 13 | navigate pushStateInterface route = do 14 | path <- Routing.Duplex.print NextjsApp.WebRouteDuplexCodec.routeCodec route 15 | # either (throwError <<< error <<< show) pure 16 | pushStateInterface.pushState (Foreign.unsafeToForeign unit) path 17 | -------------------------------------------------------------------------------- /migrations/0000000002-users/01_app_private.tables.user_secrets.up.sql: -------------------------------------------------------------------------------- 1 | create table app_private.user_secrets ( 2 | id uuid not null primary key default uuid_generate_v4(), 3 | password_hash text, 4 | reset_password_token text, 5 | reset_password_token_generated_at timestamptz, 6 | first_failed_reset_password_attempt timestamptz, 7 | first_failed_password_attempt timestamptz, 8 | reset_password_attempts int default 0 not null, 9 | password_attempts int4 default 0 not null 10 | ); 11 | 12 | alter table app_private.user_secrets enable row level security; 13 | 14 | comment on table app_private.user_secrets is 15 | E'The contents of this table should never be visible to the user. Contains data mostly related to authentication.'; 16 | -------------------------------------------------------------------------------- /nix/pkgs/waitforit/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv, fetchurl }: 2 | 3 | let 4 | revDataLinux = builtins.fromJSON (builtins.readFile ./revisionLinux.json); 5 | revDataDarwin = builtins.fromJSON (builtins.readFile ./revisionDarwin.json); 6 | 7 | revData = if stdenv.isLinux then revDataLinux else revDataDarwin; 8 | 9 | inherit (revData) url version sha256; 10 | 11 | program = fetchurl { 12 | inherit url sha256; 13 | }; 14 | in 15 | 16 | stdenv.mkDerivation { 17 | name = "waitforit-${version}"; 18 | 19 | unpackPhase = ":"; 20 | 21 | installPhase = '' 22 | mkdir -p $out/bin 23 | cp ${program} $out/bin/waitforit 24 | chmod +x $out/bin/waitforit 25 | ''; 26 | 27 | meta.platforms = stdenv.lib.platforms.all; 28 | } 29 | -------------------------------------------------------------------------------- /packages/src/Chokidar.purs: -------------------------------------------------------------------------------- 1 | module Chokidar where 2 | 3 | import Effect.Uncurried (EffectFn1, mkEffectFn1, runEffectFn1) 4 | import Protolude 5 | import Data.Array.NonEmpty (NonEmptyArray) 6 | import FRP.Event (Event) 7 | import FRP.Event as Event 8 | 9 | -- | data ChokidarEvent 10 | foreign import _watch :: 11 | EffectFn1 12 | { files :: NonEmptyArray String 13 | -- | , options :: 14 | -- | { ignored :: String 15 | -- | , ignoreInitial :: Boolean 16 | -- | , cwd :: String 17 | -- | } 18 | , onAll :: EffectFn1 String Unit 19 | } 20 | (Effect Unit) 21 | 22 | watch :: NonEmptyArray String -> Event String 23 | watch files = Event.makeEvent \push -> runEffectFn1 _watch { files, onAll: mkEffectFn1 push } 24 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/NodeEnv.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.NodeEnv where 2 | 3 | 4 | type Env = 5 | { apiUrl :: String 6 | , isProduction :: Boolean 7 | } 8 | 9 | foreign import env :: Env 10 | 11 | -- | foreign import jwtKey :: String 12 | 13 | -- | oneDay = 60.0 {- sec -} * 60.0 {- min -} * 24.0 {- h -} 14 | 15 | -- | jwtMaxAgeInSeconds :: Number 16 | -- | jwtMaxAgeInSeconds = oneDay 17 | 18 | -- | jwtDomain :: Maybe String 19 | -- | jwtDomain = 20 | -- | if env.isProduction 21 | -- | then Just "todomydomain.com" -- TODO: server on `https://api.todomydomain.com/graphql`, client on `https://todomydomain.com/graphql` 22 | -- | else Nothing -- this is actually more restrictive, it allows only on the same ORIGIN (subdomain + domain) 23 | -------------------------------------------------------------------------------- /packages/db-tests/tests/schemas/app_hidden.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_hidden'$$ 3 | 4 | begin; 5 | 6 | select no_plan(); 7 | 8 | select tables_are( 9 | :schema, 10 | array['user_emails']::text[] 11 | ); 12 | 13 | select types_are(:schema, array[]::text[]); 14 | 15 | select domains_are(:schema, array[]::text[]); 16 | 17 | select enums_are( :schema, array[]::text[]); 18 | 19 | select functions_are( 20 | :schema, 21 | array[ 22 | 'biconditional_statement', 23 | 'implication', 24 | 'is_from_0_to_100' 25 | ]::text[] 26 | ); 27 | 28 | select views_are( 29 | :schema, 30 | array[]::text[] 31 | ); 32 | 33 | select materialized_views_are( 34 | :schema, 35 | array[]::text[] 36 | ); 37 | 38 | select finish(); 39 | 40 | rollback; 41 | -------------------------------------------------------------------------------- /packages/src/Data/Email.purs: -------------------------------------------------------------------------------- 1 | module Data.Email where 2 | 3 | import Protolude 4 | import Unsafe.Coerce (unsafeCoerce) 5 | import Data.String.NonEmpty (NonEmptyString) 6 | import Data.String.NonEmpty as NonEmptyString 7 | import EmailValidator 8 | 9 | newtype Email = Email NonEmptyString 10 | 11 | toNonEmptyString :: Email -> NonEmptyString 12 | toNonEmptyString = unsafeCoerce 13 | 14 | toString :: Email -> String 15 | toString = unsafeCoerce 16 | 17 | fromNonEmptyString :: NonEmptyString -> Maybe Email 18 | fromNonEmptyString email = 19 | if emailValidate (NonEmptyString.toString email) 20 | then Just (Email email) 21 | else Nothing 22 | 23 | fromString :: String -> Maybe Email 24 | fromString = NonEmptyString.fromString >=> fromNonEmptyString 25 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/posts/priviligies/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ :: name 3 | \set table $$'posts'$$ :: name 4 | \set role $$'app_owner'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY['SELECT', 'UPDATE', 'DELETE']::text[] 15 | ); 16 | 17 | SELECT any_column_privs_are( 18 | :schema, 19 | :table, 20 | :role, 21 | ARRAY['SELECT', 'UPDATE']::text[] 22 | ); 23 | 24 | SELECT column_privs_are(:schema, :table, col, :role, ARRAY['SELECT', 'UPDATE']::text[]) 25 | FROM ( 26 | VALUES('id'), ('name'), ('user_id'), ('content'), ('created_at'), ('updated_at') 27 | ) AS tableAlias(col); 28 | 29 | 30 | select finish(); 31 | 32 | rollback; 33 | -------------------------------------------------------------------------------- /packages/src/HalogenElementsExtra.purs: -------------------------------------------------------------------------------- 1 | module HalogenElementsExtra where 2 | 3 | import Halogen.HTML as Halogen.HTML 4 | import Halogen.HTML.Properties as Halogen.HTML.Properties 5 | 6 | content :: ∀ r i. String -> Halogen.HTML.IProp ( content :: String | r ) i 7 | content = Halogen.HTML.Properties.prop (Halogen.HTML.PropName "content") 8 | 9 | async :: ∀ r i. String -> Halogen.HTML.IProp ( id :: String | r ) i 10 | async = Halogen.HTML.prop (Halogen.HTML.PropName "async") 11 | 12 | media_ :: forall r i. String -> Halogen.HTML.IProp ( media :: String | r ) i 13 | media_ = Halogen.HTML.prop (Halogen.HTML.PropName "media") 14 | 15 | as_ :: forall r i. String -> Halogen.HTML.IProp ( as :: String | r ) i 16 | as_ = Halogen.HTML.prop (Halogen.HTML.PropName "as") 17 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/web_login/priviliges.sql: -------------------------------------------------------------------------------- 1 | \set schema $$'app_private'$$ 2 | \set func $$'web_login'$$ 3 | \set args $$'{"text", "text"}'$$::text[] 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | SELECT function_privs_are( 10 | :schema, 11 | :func, 12 | :args, 13 | 'app_admin', 14 | '{"EXECUTE"}' 15 | ); 16 | 17 | SELECT function_privs_are( 18 | :schema, 19 | :func, 20 | :args, 21 | 'app_owner', 22 | '{"EXECUTE"}' 23 | ); 24 | 25 | SELECT function_privs_are( 26 | :schema, 27 | :func, 28 | :args, 29 | 'app_user', 30 | '{"EXECUTE"}' 31 | ); 32 | 33 | SELECT function_privs_are( 34 | :schema, 35 | :func, 36 | :args, 37 | 'app_anonymous', 38 | '{"EXECUTE"}' 39 | ); 40 | 41 | select finish(); 42 | 43 | rollback; 44 | -------------------------------------------------------------------------------- /packages/src/HalogenVdomStringRendererRaw.purs: -------------------------------------------------------------------------------- 1 | module HalogenVdomStringRendererRaw where 2 | 3 | import Protolude 4 | 5 | import Halogen.HTML as Halogen.HTML 6 | import Halogen.HTML.Core as Halogen.HTML.Core 7 | import Halogen.VDom.DOM.StringRenderer as HalogenVdomStringRenderer.DOM 8 | 9 | newtype RawTextWidget = RawTextWidget String 10 | 11 | rawText :: ∀ i . String -> Halogen.HTML.HTML RawTextWidget i 12 | rawText string = Halogen.HTML.Core.widget (RawTextWidget string) 13 | 14 | renderHtmlWithRawTextSupport :: ∀ i . Halogen.HTML.HTML RawTextWidget i -> String 15 | renderHtmlWithRawTextSupport html = HalogenVdomStringRenderer.DOM.render renderWidget (unwrap html) 16 | where 17 | renderWidget (RawTextWidget string) = string 18 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Login.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Login (page) where 2 | 3 | import Protolude 4 | import NextjsApp.PageImplementations.Login as NextjsApp.PageImplementations.Login 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import NextjsApp.AppM (AppM(..)) 7 | import NextjsApp.Route as NextjsApp.Route 8 | 9 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 10 | pageSpec = 11 | { pageData: PageData__Static unit 12 | , component: NextjsApp.PageImplementations.Login.component 13 | , title: "Login" 14 | } 15 | 16 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 17 | page = mkPageSpecBoxed pageSpec 18 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Register.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Register (page) where 2 | 3 | import Protolude 4 | import NextjsApp.PageImplementations.Register as NextjsApp.PageImplementations.Register 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import NextjsApp.AppM (AppM(..)) 7 | import NextjsApp.Route as NextjsApp.Route 8 | 9 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 10 | pageSpec = 11 | { pageData: PageData__Static unit 12 | , component: NextjsApp.PageImplementations.Register.component 13 | , title: "Register" 14 | } 15 | 16 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 17 | page = mkPageSpecBoxed pageSpec 18 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Secret/Types.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.PageImplementations.Secret.Types where 2 | 3 | import Protolude 4 | import Data.Argonaut (class DecodeJson, class EncodeJson) 5 | import Data.Argonaut.Decode.Generic (genericDecodeJson) 6 | import Data.Argonaut.Encode.Generic (genericEncodeJson) 7 | 8 | newtype SecretPageUserData = SecretPageUserData 9 | { id :: String 10 | , name :: Maybe String 11 | , username :: String 12 | } 13 | 14 | derive instance genericSecretPageUserData :: Generic SecretPageUserData _ 15 | 16 | instance encodeJsonSecretPageUserData :: EncodeJson SecretPageUserData where 17 | encodeJson = genericEncodeJson 18 | 19 | instance decodeJsonSecretPageUserData :: DecodeJson SecretPageUserData where 20 | decodeJson = genericDecodeJson 21 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Lazy.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.Lazy (page) where 2 | 3 | import Protolude 4 | import Example.Lazy.Main as Example.Lazy.Main 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Lazy.Main.component 14 | , title: "Halogen Example - lazy" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Scopes.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Scopes where 2 | 3 | data Scope__DeleteUserAuthenticationPayload 4 | 5 | data Scope__DeleteUserPayload 6 | 7 | data Scope__ForgotPasswordPayload 8 | 9 | data Scope__ListenPayload 10 | 11 | data Scope__PageInfo 12 | 13 | data Scope__Post 14 | 15 | data Scope__PostsConnection 16 | 17 | data Scope__PostsEdge 18 | 19 | data Scope__ResetPasswordPayload 20 | 21 | data Scope__UpdateUserPayload 22 | 23 | data Scope__User 24 | 25 | data Scope__UserAuthentication 26 | 27 | data Scope__UserAuthenticationsEdge 28 | 29 | data Scope__UserEmail 30 | 31 | data Scope__UsersEdge 32 | 33 | data Scope__VerifyUserEmailPayload 34 | 35 | data Scope__WebLoginPayload 36 | 37 | data Scope__WebRegisterPayload 38 | 39 | data Scope__Node 40 | -------------------------------------------------------------------------------- /packages/api-server/ApiServer/FrontendServerHttpProxy.purs_: -------------------------------------------------------------------------------- 1 | module ApiServer.FrontendServerHttpProxy where 2 | 3 | import Data.Function.Uncurried 4 | import Effect 5 | import Effect.Uncurried 6 | import Node.Express.App 7 | import Node.Express.Passport 8 | import Node.Express.Types 9 | import Protolude 10 | 11 | import Data.Argonaut (Json) 12 | import Data.Maybe (Maybe(..)) 13 | import Data.Newtype (class Newtype) 14 | import Data.Nullable (Nullable) 15 | import Data.Nullable as Nullable 16 | import Effect.Aff (Aff, runAff_) 17 | import Effect.Exception (Error) 18 | import Node.HTTP (Server) 19 | 20 | foreign import _installFrontendServerProxy :: 21 | EffectFn3 22 | Server 23 | Application 24 | String 25 | Unit 26 | 27 | installFrontendServerProxy = runEffectFn3 _installFrontendServerProxy 28 | -------------------------------------------------------------------------------- /packages/src/ContribWebpackPlugins.js: -------------------------------------------------------------------------------- 1 | exports._MiniCssExtractPlugin = function(opts) { 2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin") 3 | return new MiniCssExtractPlugin(opts) 4 | } 5 | 6 | exports._CleanWebpackPlugin = new (require("clean-webpack-plugin").CleanWebpackPlugin)() 7 | 8 | exports._BundleAnalyzerPlugin = function (opts) { return new (require("webpack-bundle-analyzer").BundleAnalyzerPlugin)(opts) } 9 | 10 | exports._HtmlWebpackPlugin = function (opts) { new (require("html-webpack-plugin"))(opts) } 11 | 12 | exports.htmlWebpackPlugin__tags__toString = function (tags) { 13 | const r = tags.toString() 14 | 15 | console.log('htmlWebpackPlugin__tags__toString', r) 16 | 17 | if (!Array.isArray(r)) { throw new Error('not an array') } 18 | 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /migrations/0000000003-posts/01_posts/01_table.up.sql: -------------------------------------------------------------------------------- 1 | create table app_public.posts ( 2 | id uuid primary key default uuid_generate_v4(), 3 | name varchar(255) not null CHECK (name <> ''), 4 | content varchar(255) not null CHECK (name <> ''), 5 | 6 | user_id 7 | uuid 8 | not null 9 | references app_public.users(id) 10 | on delete cascade 11 | on update cascade, 12 | 13 | created_at timestamptz not null default now(), 14 | updated_at timestamptz not null default now() 15 | ); 16 | 17 | create index app_public_posts_user_id on app_public.posts (user_id); 18 | -------------------------------------------------------------------------------- /config/public/keys.nix: -------------------------------------------------------------------------------- 1 | rec { 2 | srghmaKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDGoNIxTQUeb8GbuBR87l133ozDBlPi+WbpYCfrSBD1s5MBxlulWiM38Np+1+uhcndjZEZaa5qjVWlWDicn2f8sAQzFJ024OjBAueJpH5ZhUkgTFh/kEXzV3Xbx7dL6MStxSlzTSLuXsWLHfX9NsFxwmyYihs2tQ7zqM8157zxr/J/MJ0OCVMUxHrmJICk1cRZyTv4I5sRxs+wRf3qpYIhRYXkR6kIW1A0f1/7VmYRAc+pI1GBkaQuI+0anvPvv7SWcW/l9piw0DAxkG85GLie9qP6Ag8Rj/zp0ri3Pu1jFO7uCC5l6DTa8vEgsPrTH5mQEdSrXSObvzuCdlWBAEnooKml/FVLKMm8xbyF4FtxK4m5mt31EdVAzmaILUpIF0TSdAzo4H7D3/Eq6poR33AWa5CPbhPHhjHbDIVSe2kKtzNo9RqdIPgDu5wEs9YkHg+qmnWkLBCNbKQDmAjfuwAS674lEWWYYUn/HJhG6og3hMrGFwURQ6US+Ogx0PNHLc61CAnite+bptkItodu/6C7lfRgWLrWxHPxjajv/UXwVZoTKLRYRVT9tm2Ib38TJcdGp6Kc/+BCuCzI/I0l4tXdkI8sx4g31Rk8c2IO7GHsBXs/6zyrzQOFAYX7FvOK6KhaWtIZ2sGur5MSX6PU323upjv7ri76IEhrXoZ+GwqyfUQ== srghma@gmail.com"; 3 | 4 | devKeys = [ srghmaKey ]; 5 | } 6 | -------------------------------------------------------------------------------- /migrations/0000000002-users/09_app_public.functions.user_by_username_or_email.up.sql: -------------------------------------------------------------------------------- 1 | create function app_public.user_by_username_or_verified_email(username_or_email text) returns app_public.users as $$ 2 | select users.* 3 | from app_public.users 4 | where 5 | -- Match username against users username, or any verified email address 6 | ( 7 | users.username = user_by_username_or_verified_email.username_or_email 8 | or 9 | exists( 10 | select 1 11 | from app_hidden.user_emails 12 | where user_id = users.id 13 | and is_verified is true 14 | and email = user_by_username_or_verified_email.username_or_email::citext 15 | ) 16 | ); 17 | $$ language sql 18 | stable 19 | strict -- all args are required 20 | set search_path from current; 21 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Ace.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.Ace (page) where 2 | 3 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 4 | import Protolude 5 | import Halogen as H 6 | import Example.Ace.Container as Example.Ace.Container 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Ace.Container.component 14 | , title: "Halogen Example - Ace button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/posts/common.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ :: name 3 | \set table $$'posts'$$ :: name 4 | 5 | begin; 6 | 7 | select no_plan(); 8 | 9 | select has_table(:schema, :table); 10 | 11 | select columns_are( 12 | :schema, 13 | :table, 14 | ARRAY[ 15 | 'id', 16 | 'user_id', 17 | 'name', 18 | 'content', 19 | 'created_at', 20 | 'updated_at' 21 | ] 22 | ); 23 | 24 | select indexes_are( 25 | :schema, 26 | :table, 27 | ARRAY[ 28 | 'posts_pkey', 29 | 'app_public_posts_user_id' 30 | ] 31 | ); 32 | 33 | select rules_are( 34 | :schema, 35 | :table, 36 | ARRAY[]::text[] 37 | ); 38 | 39 | select triggers_are( 40 | :schema, 41 | :table, 42 | ARRAY['set_updated_at'] 43 | ); 44 | 45 | select finish(); 46 | 47 | rollback; 48 | -------------------------------------------------------------------------------- /migrations/0000000001-start/01_prelude.up.sql: -------------------------------------------------------------------------------- 1 | -- tables and functions to be exposed to graphql 2 | create schema app_public; 3 | grant usage on schema app_public to app_anonymous, app_user; 4 | alter default privileges in schema app_public grant usage, select on sequences to app_anonymous, app_user; 5 | 6 | -- same privileges as app_public, but simply not exposed to graphql 7 | create schema app_hidden; 8 | grant usage on schema app_hidden to app_anonymous, app_user; 9 | alter default privileges in schema app_hidden grant usage, select on sequences to app_anonymous, app_user; 10 | 11 | -- secrets that require elevated privileges to access 12 | create schema app_private; 13 | grant usage on schema app_hidden to app_user; 14 | alter default privileges in schema app_hidden grant usage, select on sequences to app_user; 15 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Basic.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.Basic (page) where 2 | 3 | import Protolude 4 | import Example.Basic.Button as Example.Basic.Button 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Basic.Button.component 14 | , title: "Halogen Example - Basic button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Queries/IsUsernameOrEmailInUse.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Queries.IsUsernameOrEmailInUse where 2 | 3 | import Protolude 4 | 5 | import NextjsGraphqlApi.Object.User as NextjsGraphqlApi.Object.User 6 | import NextjsGraphqlApi.Query as NextjsGraphqlApi.Query 7 | import Data.Maybe as Maybe 8 | import Data.String.NonEmpty (NonEmptyString) 9 | import Data.String.NonEmpty as NonEmptyString 10 | import NextjsApp.Queries.Utils (graphqlApiQueryRequestOrThrow) 11 | 12 | isUsernameOrEmailInUse :: NonEmptyString -> Aff Boolean 13 | isUsernameOrEmailInUse usernameOrEmail = graphqlApiQueryRequestOrThrow $ 14 | NextjsGraphqlApi.Query.userByUsernameOrVerifiedEmail 15 | { usernameOrEmail: NonEmptyString.toString usernameOrEmail 16 | } 17 | (NextjsGraphqlApi.Object.User.id) 18 | <#> Maybe.isJust 19 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/lazy/src/LazyLoadedImport.js: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // wait for https://github.com/erikd/language-javascript/issues/119 3 | // and remove LazyLoadedImportImplementation file which exists only to hack compilation error "unexpected keyword `import`" 4 | // 5 | // when we import like this - the `import` keyword from `LazyLoadedImportImplementation.js` is not parsed by purescript, but rather linked to THIS foreign import file using webpack-spago-loader 6 | // 7 | // 8 | // so, it imports like this: 9 | // - LazyLoadedImport.purs (wrapper) 10 | // -> LazyLoadedImport.js (this is where the `import` should be) 11 | // -> ./LazyLoadedImportImplementation.js (hack) 12 | // -> LazyLoaded.purs (the page definition) 13 | exports.lazyLoadedImport_ = require('./LazyLoadedImportImplementation.js').lazyLoadedImport 14 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/really_create_user/priviliges.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_private'$$ 3 | \set func $$'really_create_user'$$ 4 | \set args $$'{"text", "text", "bool", "text", "text", "text"}'$$::text[] 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT function_privs_are( 11 | :schema, 12 | :func, 13 | :args, 14 | 'app_admin', 15 | '{"EXECUTE"}' 16 | ); 17 | 18 | SELECT function_privs_are( 19 | :schema, 20 | :func, 21 | :args, 22 | 'app_owner', 23 | '{"EXECUTE"}' 24 | ); 25 | 26 | SELECT function_privs_are( 27 | :schema, 28 | :func, 29 | :args, 30 | 'app_user', 31 | '{"EXECUTE"}' 32 | ); 33 | 34 | SELECT function_privs_are( 35 | :schema, 36 | :func, 37 | :args, 38 | 'app_anonymous', 39 | '{"EXECUTE"}' 40 | ); 41 | 42 | select finish(); 43 | 44 | rollback; 45 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Interpret.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.Interpret (page) where 2 | 3 | import Protolude 4 | import Example.Interpret.Main as Example.Interpret.Main 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Interpret.Main.ui' 14 | , title: "Halogen Example - Interpret button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Lifecycle.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.Lifecycle (page) where 2 | 3 | import Protolude 4 | import Example.Lifecycle.Main as Example.Lifecycle.Main 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Lifecycle.Main.ui 14 | , title: "Halogen Example - Lifecycle button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/TextNodes.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.TextNodes (page) where 2 | 3 | import Protolude 4 | import Example.TextNodes.Main as Example.TextNodes.Main 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.TextNodes.Main.component 14 | , title: "Halogen Example - Text nodes" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/insert/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | \set user1_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set user2_id $$'00000000-0000-0000-0000-000000000002'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | insert into app_public.users 11 | (id, email, first_name, last_name) 12 | values 13 | (:user1_id, random_email(), random_string(), random_string()); 14 | 15 | set local role :role; 16 | set local jwt.claims.user_id to :user1_id; 17 | select is(current_user, :role); 18 | 19 | prepare inserter as 20 | insert into app_public.users 21 | values (:user2_id, 'user2@mail.com', random_string(), random_string()) 22 | returning *; 23 | 24 | -- cant insert another user 25 | 26 | select throws_ok('inserter'); 27 | 28 | select finish(); 29 | 30 | rollback; 31 | -------------------------------------------------------------------------------- /packages/src/BuildSeleniumChromeDriver.purs_: -------------------------------------------------------------------------------- 1 | module BuildSeleniumChromeDriver where 2 | 3 | import Prelude 4 | import Test.Spec (Spec, describe, it) 5 | import Data.Maybe (maybe) 6 | import Data.Time.Duration (Milliseconds(..)) 7 | import Effect (Effect) 8 | import Effect.Aff (launchAff, delay) 9 | import Effect.Class (liftEffect) 10 | import Effect.Console (log) 11 | import Selenium 12 | import Selenium.Browser 13 | import Selenium.Builder 14 | import Selenium.Types 15 | import Control.Monad.Error.Class (throwError) 16 | import Effect.Exception (error) 17 | import Unsafe.Coerce 18 | import Effect.Aff 19 | import Effect.Aff.Compat 20 | 21 | buildSeleniumChromeDriver :: Aff Driver 22 | buildSeleniumChromeDriver = fromEffectFnAff _buildSeleniumChromeDriver 23 | 24 | foreign import _buildSeleniumChromeDriver ∷ EffectFnAff Driver 25 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Login/Types.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.PageImplementations.Login.Types where 2 | 3 | import Protolude 4 | 5 | import Halogen.Hooks.Formless as F 6 | import Halogen as H 7 | import NextjsApp.PageImplementations.Login.Form (FormChildSlots, LoginDataValidated, LoginForm) 8 | 9 | type Query 10 | = Const Void 11 | 12 | type Input 13 | = Unit 14 | 15 | type Message 16 | = Void 17 | 18 | data Action 19 | = Action__HandleLoginForm LoginDataValidated 20 | 21 | type ChildSlots 22 | = ( formless :: H.Slot (F.Query LoginForm (Const Void) FormChildSlots) LoginDataValidated Unit ) 23 | 24 | data LoginError 25 | = LoginError__LoginFailed 26 | | LoginError__UnknownError String 27 | -- | | LoginError__UsernameOrEmailNotRegistered 28 | 29 | type State = { loginError :: Maybe LoginError } 30 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/DeeplyNested.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.DeeplyNested (page) where 2 | 3 | import Protolude 4 | import Example.DeeplyNested.A as Example.DeeplyNested.A 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.DeeplyNested.A.component 14 | , title: "Halogen Example - DeeplyNested button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Object/PostsEdge.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Object.PostsEdge where 2 | 3 | import GraphQLClient 4 | ( SelectionSet 5 | , selectionForField 6 | , graphqlDefaultResponseScalarDecoder 7 | , selectionForCompositeField 8 | , graphqlDefaultResponseFunctorOrScalarDecoderTransformer 9 | ) 10 | import NextjsGraphqlApi.Scopes (Scope__PostsEdge, Scope__Post) 11 | import Data.Maybe (Maybe) 12 | import NextjsGraphqlApi.Scalars (Cursor) 13 | 14 | cursor :: SelectionSet Scope__PostsEdge (Maybe Cursor) 15 | cursor = selectionForField "cursor" [] graphqlDefaultResponseScalarDecoder 16 | 17 | node :: forall r . SelectionSet Scope__Post r -> SelectionSet Scope__PostsEdge r 18 | node = selectionForCompositeField 19 | "node" 20 | [] 21 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 22 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Object/UsersEdge.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Object.UsersEdge where 2 | 3 | import GraphQLClient 4 | ( SelectionSet 5 | , selectionForField 6 | , graphqlDefaultResponseScalarDecoder 7 | , selectionForCompositeField 8 | , graphqlDefaultResponseFunctorOrScalarDecoderTransformer 9 | ) 10 | import NextjsGraphqlApi.Scopes (Scope__UsersEdge, Scope__User) 11 | import Data.Maybe (Maybe) 12 | import NextjsGraphqlApi.Scalars (Cursor) 13 | 14 | cursor :: SelectionSet Scope__UsersEdge (Maybe Cursor) 15 | cursor = selectionForField "cursor" [] graphqlDefaultResponseScalarDecoder 16 | 17 | node :: forall r . SelectionSet Scope__User r -> SelectionSet Scope__UsersEdge r 18 | node = selectionForCompositeField 19 | "node" 20 | [] 21 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 22 | -------------------------------------------------------------------------------- /config/ignored/passwords-dev.setup.sh: -------------------------------------------------------------------------------- 1 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 2 | 3 | cat > $SCRIPT_DIR/passwords.nix < Aff Unit 25 | } 26 | 27 | -------------------------------------------------------------------------------- /config/ignored/passwords.setup.sh: -------------------------------------------------------------------------------- 1 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 2 | 3 | cat > $SCRIPT_DIR/passwords.nix </dev/null 2>&1 9 | 10 | pg_dump --schema-only --no-owner --file=schemas/schema.sql "$DATABASE_URL" 11 | 12 | if [ -f node_modules/postgraphile/node_modules/graphile-build-pg/res/watch-fixtures.sql ]; then 13 | cat node_modules/postgraphile/node_modules/graphile-build-pg/res/watch-fixtures.sql | psql --no-psqlrc "$DATABASE_URL" >/dev/null 2>&1 14 | else 15 | cat node_modules/graphile-build-pg/res/watch-fixtures.sql | psql --no-psqlrc "$DATABASE_URL" >/dev/null 2>&1 16 | fi; 17 | 18 | echo " done" 19 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/HigherOrderComponents.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.HigherOrderComponents (page) where 2 | 3 | import Protolude 4 | import Example.HOC.Harness as Example.HOC.Harness 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.HOC.Harness.component 14 | , title: "Halogen Example - HigherOrderComponents button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Manifest/ClientPagesManifest.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Manifest.ClientPagesManifest where 2 | 3 | import Protolude 4 | import NextjsApp.Manifest.PageManifest as NextjsApp.Manifest.PageManifest 5 | import NextjsApp.Route as NextjsApp.Route 6 | import Nextjs.Utils as Nextjs.Utils 7 | import Web.DOM.ParentNode as Web.DOM.ParentNode 8 | import Data.Argonaut.Decode as ArgonautCodecs 9 | import NextjsApp.Constants as NextjsApp.Constants 10 | 11 | type ClientPagesManifest 12 | = Record (NextjsApp.Route.WebRoutesVacantRow NextjsApp.Manifest.PageManifest.PageManifest) 13 | 14 | getBuildManifest :: Aff ClientPagesManifest 15 | getBuildManifest = do 16 | json <- Nextjs.Utils.findJsonFromScriptElement (Web.DOM.ParentNode.QuerySelector NextjsApp.Constants.pagesManifestId) 17 | Nextjs.Utils.requiredJsonDecodeResult $ ArgonautCodecs.decodeJson json 18 | -------------------------------------------------------------------------------- /packages/feature-tests/FeatureTests/AllTests.purs: -------------------------------------------------------------------------------- 1 | module FeatureTests.AllTests where 2 | 3 | import Protolude 4 | import Test.Spec (SpecT, describe) 5 | import Data.Identity 6 | import FeatureTests.FeatureTestSpec 7 | import FeatureTests.Tests.Login.SuccessSpec as FeatureTests.Tests.Login.SuccessSpec 8 | -- | import FeatureTests.Tests.Login.ErrorNotConfirmedSpec as FeatureTests.Tests.Login.ErrorNotConfirmedSpec 9 | import FeatureTests.Tests.Register.SuccessSpec as FeatureTests.Tests.Register.SuccessSpec 10 | 11 | allTests :: SpecT Aff FeatureSpecConfig Identity Unit 12 | allTests = do 13 | describe "login" do 14 | it "success" FeatureTests.Tests.Login.SuccessSpec.spec 15 | -- | it "error not confirmed" FeatureTests.Tests.Login.ErrorNotConfirmedSpec.spec 16 | describe "register" do 17 | itOnly "success" FeatureTests.Tests.Register.SuccessSpec.spec 18 | -------------------------------------------------------------------------------- /nix/pkgs/waitforit/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash -p nix-prefetch-git 3 | 4 | SCRIPT_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")") 5 | version="v2.4.1" 6 | 7 | urlLinux="https://github.com/maxcnunes/waitforit/releases/download/$version/waitforit-linux_amd64" 8 | urlDarwin="https://github.com/maxcnunes/waitforit/releases/download/$version/waitforit-darwin_amd64" 9 | 10 | ######### 11 | 12 | sha256=$(nix-prefetch-url --type sha256 $urlLinux) 13 | 14 | cat > $SCRIPT_DIR/revisionLinux.json < $SCRIPT_DIR/revisionDarwin.json < { 5 | url = "https://github.com/${spec.owner}/${spec.repo}/archive/${spec.rev}.tar.gz"; 6 | inherit (spec) sha256; 7 | }; 8 | 9 | nixcfg = import ; 10 | 11 | nixpkgs = builtins.derivation { 12 | inherit src; 13 | system = builtins.currentSystem; 14 | name = "${src.name}-unpacked"; 15 | builder = builtins.storePath nixcfg.shell; 16 | args = [ 17 | (builtins.toFile "builder" '' 18 | $coreutils/mkdir $out 19 | cd $out 20 | $gzip -d < $src | $tar -x --strip-components=1 21 | '') 22 | ]; 23 | coreutils = builtins.storePath nixcfg.coreutils; 24 | tar = builtins.storePath nixcfg.tar; 25 | gzip = builtins.storePath nixcfg.gzip; 26 | }; 27 | in 28 | nixpkgs 29 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/web_login/error_when_email_is_wrong.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set email $$'user@mail.com'$$ 5 | \set password $$'secret_pass'$$ 6 | \set other_email $$'non_existent@mail.com'$$ 7 | 8 | begin; 9 | 10 | select no_plan(); 11 | 12 | insert into app_public.users 13 | (id, email, first_name, last_name, is_confirmed, password_hash) 14 | values 15 | (:user_id, :email, random_string(), random_string(), random_boolean(), crypt(:password, gen_salt('bf'))); 16 | 17 | set local role :role; 18 | select is(current_user, :role); 19 | 20 | prepare do_stuff as 21 | select app_public.login(:other_email, :password); 22 | 23 | select throws_ok( 24 | 'do_stuff', 25 | 'APP_EXCEPTION__LOGIN__EMAIL_NOT_REGISTERED' 26 | ); 27 | 28 | select finish(); 29 | 30 | rollback; 31 | -------------------------------------------------------------------------------- /packages/client-webpack/NextjsWebpack/BuildManifestPlugin.js: -------------------------------------------------------------------------------- 1 | // https://github.com/vercel/next.js/blob/497cac4b93de9ecf6c7ed79bd6557dcd3bb51be5/packages/next/build/webpack/plugins/build-manifest-plugin.ts#L243-L255 2 | // https://github.com/webpack/webpack/issues/11425 3 | 4 | const webpack = require('webpack') 5 | 6 | exports.mkNextJsBuildManifest = function(doWork) { 7 | class NextJsBuildManifest { 8 | apply(compiler) { 9 | compiler.hooks.make.tap('NextJsBuildManifest', (compilation) => { 10 | compilation.hooks.processAssets.tap( 11 | { 12 | name: 'NextJsBuildManifest', 13 | stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, 14 | }, 15 | (assets) => { 16 | doWork(compilation, assets) 17 | } 18 | ) 19 | }) 20 | } 21 | } 22 | 23 | return new NextJsBuildManifest() 24 | } 25 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/register/error_when_email_already_registered.sql_: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | 4 | \set first_name $$'user1_first_name'$$ 5 | \set last_name $$'user1_first_name'$$ 6 | 7 | \set email $$'user@mail.com'$$ 8 | \set password $$'secret_pass'$$ 9 | 10 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 11 | 12 | begin; 13 | 14 | select no_plan(); 15 | 16 | insert into app_public.users 17 | (id, email, first_name, last_name, is_confirmed) 18 | values 19 | (:user_id, :email, random_string(), random_string(), random_boolean()); 20 | 21 | prepare do_stuff as 22 | select id from app_public.register( 23 | :first_name, 24 | :last_name, 25 | :email, 26 | :password 27 | ); 28 | 29 | select throws_ok( 30 | 'do_stuff', 31 | 'APP_EXCEPTION__REGISTER__ALREADY_REGISTERED' 32 | ); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /packages/src/ArgonautCodecDecodersExtran.purs: -------------------------------------------------------------------------------- 1 | module ArgonautCodecDecodersExtran where 2 | 3 | import Protolude 4 | import Data.Argonaut.Decode 5 | import Data.Argonaut.Core (Json) 6 | import Data.Argonaut.Decode.Decoders (decodeString) 7 | import Data.Argonaut.Decode.Decoders as Decoders 8 | import Data.Argonaut.Decode.Generic (genericDecodeJson) 9 | import Data.Show.Generic (genericShow) 10 | import Data.String as String 11 | import Data.String.NonEmpty (NonEmptyString) 12 | import Data.String.NonEmpty as NonEmptyString 13 | 14 | decodeNonEmptyString :: Json -> Either JsonDecodeError NonEmptyString 15 | decodeNonEmptyString json = 16 | note (Named "NonEmptyString" $ UnexpectedValue json) 17 | =<< map (NonEmptyString.fromString) (decodeString json) 18 | 19 | decodeNonempty :: Json -> Either JsonDecodeError String 20 | decodeNonempty = map NonEmptyString.toString <<< decodeNonEmptyString 21 | -------------------------------------------------------------------------------- /packages/src/Cordova/EventTypes.purs: -------------------------------------------------------------------------------- 1 | module Cordova.EventTypes where 2 | 3 | import Web.Event.Event (EventType(..)) 4 | 5 | deviceready :: EventType 6 | deviceready = EventType "deviceready" 7 | 8 | pause :: EventType 9 | pause = EventType "pause" 10 | 11 | resume :: EventType 12 | resume = EventType "resume" 13 | 14 | backbutton :: EventType 15 | backbutton = EventType "backbutton" 16 | 17 | menubutton :: EventType 18 | menubutton = EventType "menubutton" 19 | 20 | searchbutton :: EventType 21 | searchbutton = EventType "searchbutton" 22 | 23 | startcallbutton :: EventType 24 | startcallbutton = EventType "startcallbutton" 25 | 26 | endcallbutton :: EventType 27 | endcallbutton = EventType "endcallbutton" 28 | 29 | volumedownbutton :: EventType 30 | volumedownbutton = EventType "volumedownbutton" 31 | 32 | volumeupbutton :: EventType 33 | volumeupbutton = EventType "volumeupbutton" 34 | -------------------------------------------------------------------------------- /packages/db-tests/tests/no_missing_indexes_on_foreign_keys.sql: -------------------------------------------------------------------------------- 1 | -- check for fks where there is no matching index 2 | -- on the referencing side 3 | -- or a bad index 4 | 5 | begin; 6 | 7 | select plan(1); 8 | 9 | -- this is what you want 10 | select is_empty('select find_missing_indexes_on_foreign_keys()'); 11 | 12 | -- but sometimes you'll want it to return non-empty records 13 | 14 | /* select set_eq( */ 15 | /* -- no need to create this index, because unique constraint creates it */ 16 | /* 'select * from find_missing_indexes_on_foreign_keys()', */ 17 | /* $$values */ 18 | /* ( */ 19 | /* 'app_public'::name, */ 20 | /* 'users__mtm__projects'::name, */ 21 | /* 'users__mtm__projects_project_id_fkey'::name, */ 22 | /* 'no index', */ 23 | /* 'projects'::name */ 24 | /* ) */ 25 | /* $$ */ 26 | /* ); */ 27 | 28 | select finish(); 29 | 30 | rollback; 31 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/delete/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | \set user1_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set user2_id $$'00000000-0000-0000-0000-000000000002'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | insert into app_public.users 11 | (id, email, first_name, last_name) 12 | values 13 | (:user1_id, random_email(), random_string(), random_string()), 14 | (:user2_id, random_email(), random_string(), random_string()); 15 | 16 | set local role :role; 17 | set local jwt.claims.user_id to :user1_id; 18 | select is(current_user, :role); 19 | 20 | delete from app_public.users where id in (:user1_id, :user2_id); 21 | -- cant delete another user 22 | prepare expected as values(:user2_id :: uuid); 23 | 24 | select set_eq( 25 | 'select id from app_public.users', 26 | 'expected' 27 | ); 28 | 29 | select finish(); 30 | 31 | rollback; 32 | -------------------------------------------------------------------------------- /packages/api-server/ApiServer/PostgraphilePassportAuthPlugin.js: -------------------------------------------------------------------------------- 1 | const { makeExtendSchemaPlugin, gql } = require("graphile-utils"); 2 | 3 | exports.mkPassportLoginPlugin = mkResolvers => makeExtendSchemaPlugin(build => ({ 4 | typeDefs: gql` 5 | input WebRegisterInput { 6 | username: String! 7 | email: String! 8 | password: String! 9 | name: String 10 | avatarUrl: String 11 | } 12 | 13 | type WebRegisterPayload { 14 | user: User! @pgField 15 | } 16 | 17 | input WebLoginInput { 18 | username: String! 19 | password: String! 20 | } 21 | 22 | type WebLoginPayload { 23 | user: User! @pgField 24 | } 25 | 26 | extend type Mutation { 27 | webRegister(input: WebRegisterInput!): WebRegisterPayload 28 | webLogin(input: WebLoginInput!): WebLoginPayload 29 | } 30 | `, 31 | resolvers: mkResolvers(build) 32 | })); 33 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/EffectsEffectRandom.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Pages.Examples.EffectsEffectRandom (page) where 2 | 3 | import Protolude 4 | import Example.Effects.Effect.Random.Component as Example.Effects.Effect.Random.Component 5 | import Nextjs.Page (PageSpecBoxed, PageData(..), PageSpec, mkPageSpecBoxed) 6 | import Halogen as H 7 | import NextjsApp.AppM (AppM(..)) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | pageSpec :: PageSpec NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) Unit 11 | pageSpec = 12 | { pageData: PageData__Static unit 13 | , component: H.hoist liftAff $ Example.Effects.Effect.Random.Component.component 14 | , title: "Halogen Example - EffectsEffectRandom button" 15 | } 16 | 17 | page :: PageSpecBoxed NextjsApp.Route.WebRoutesWithParamRow (AppM NextjsApp.Route.WebRoutesWithParamRow) 18 | page = mkPageSpecBoxed pageSpec 19 | -------------------------------------------------------------------------------- /packages/src/ReadWriteStdinYesNo.purs_: -------------------------------------------------------------------------------- 1 | readStdin :: Aff String 2 | readStdin = makeAff \callback -> do 3 | ref <- Ref.new "" 4 | Node.Stream.onDataString Node.Process.stdin Node.Encoding.UTF8 \s -> do 5 | buffer <- Ref.read ref 6 | Ref.write (buffer <> s) ref 7 | Node.Stream.onEnd Node.Process.stdin do 8 | buffer <- Ref.read ref 9 | callback (pure buffer) 10 | pure nonCanceler 11 | 12 | readEndStdin :: Aff Unit 13 | readEndStdin = makeAff \callback -> do 14 | Node.Stream.onEnd Node.Process.stdin do 15 | traceM "end" 16 | callback $ Right unit 17 | pure nonCanceler 18 | 19 | writeStdout :: String -> Effect Unit 20 | writeStdout s = 21 | void (Node.Stream.writeString Node.Process.stdout Node.Encoding.UTF8 s (pure unit)) 22 | 23 | pressCtrlDToContinue :: Aff Unit 24 | pressCtrlDToContinue = do 25 | liftEffect $ writeStdout "Press \"CTRL-D\" to continue: " 26 | readEndStdin 27 | -------------------------------------------------------------------------------- /packages/src/TimeExtra.purs: -------------------------------------------------------------------------------- 1 | module TimeExtra where 2 | 3 | import Prelude 4 | import Data.Time.Duration (class Duration, Milliseconds) 5 | import Effect.Now (nowDateTime) 6 | import Data.DateTime (diff) 7 | import Data.Time.Duration (class Duration) 8 | import Debug.Trace (traceM) 9 | import Data.Tuple (Tuple(..)) 10 | import Effect.Class (liftEffect, class MonadEffect) 11 | 12 | time :: forall a d m. MonadEffect m => Duration d => m a -> m (Tuple d a) 13 | time action = do 14 | dateTimeVal <- liftEffect nowDateTime 15 | output <- action 16 | dateTimeVal' <- liftEffect nowDateTime 17 | pure (Tuple (diff dateTimeVal' dateTimeVal) output) 18 | 19 | traceTime :: forall a m. MonadEffect m => String -> m a -> m a 20 | traceTime name action = do 21 | Tuple (milliseconds :: Milliseconds) output <- time action 22 | traceM $ "Time diff for action \"" <> name <> "\": " <> show milliseconds 23 | pure output 24 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/D.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.D (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | import Example.DeeplyNested.F as F 8 | import Data.Const (Const) 9 | import Type.Proxy (Proxy(..)) 10 | 11 | type State = Unit 12 | 13 | data Action = Void 14 | 15 | type ChildSlots = 16 | ( f :: H.Slot (Const Void) Void Unit 17 | ) 18 | 19 | _f :: Proxy "f" 20 | _f = Proxy 21 | 22 | component :: forall q i o m. H.Component q i o m 23 | component = 24 | H.mkComponent 25 | { initialState: const unit 26 | , render 27 | , eval: H.mkEval H.defaultEval 28 | } 29 | 30 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 31 | render state = 32 | HH.ul_ 33 | [ HH.li_ [ HH.text "d" ] 34 | , HH.li_ [ HH.slot _f unit F.component unit absurd ] 35 | , HH.li_ [ HH.text "d end" ] 36 | ] 37 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/interpret/README.md: -------------------------------------------------------------------------------- 1 | # Interpret 2 | 3 | This example illustrates using `ReaderT` with `Aff` as the effect type for a component, and then interpreting this back down to `Aff` so it can be run as a normal component. This can be used, for example, to implement a read-only configuration for your application, or a global state if you store a mutable reference. 4 | 5 | For an example application which uses `ReaderT` with `Aff` to implement a global state, see [Real World Halogen](https://github.com/thomashoneyman/purescript-halogen-realworld/). 6 | 7 | ## Building 8 | 9 | You can build this example from the root of the Halogen project: 10 | 11 | ```sh 12 | npm install 13 | npm run example-interpret 14 | ``` 15 | 16 | This will bundle a runnable JS file, `example.js`, in the `examples/interpret/dist` directory. You can view the running application by opening the corresponding `index.html` file. 17 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/priviliges/app_user.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set table $$'users'$$ 4 | \set role $$'app_user'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY[ 15 | 'SELECT' 16 | ] 17 | ); 18 | 19 | SELECT any_column_privs_are( 20 | :schema, 21 | :table, 22 | :role, 23 | ARRAY[ 24 | 'SELECT' 25 | ] 26 | ); 27 | 28 | SELECT column_privs_are(:schema, :table, 'id', :role, ARRAY['SELECT']); 29 | SELECT column_privs_are(:schema, :table, 'first_name', :role, ARRAY['SELECT']); 30 | SELECT column_privs_are(:schema, :table, 'last_name', :role, ARRAY['SELECT']); 31 | SELECT column_privs_are(:schema, :table, 'created_at', :role, ARRAY['SELECT']); 32 | SELECT column_privs_are(:schema, :table, 'updated_at', :role, ARRAY['SELECT']); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /migrations/0000000001-start/04_tg__set_updated_at.up.sql: -------------------------------------------------------------------------------- 1 | create function app_private.tg__set_updated_at() returns trigger as $$ 2 | begin 3 | new.updated_at := current_timestamp; 4 | return new; 5 | end; 6 | $$ language plpgsql; 7 | 8 | /* create function app_private.tg__set_updated_at() returns trigger as $$ */ 9 | /* begin */ 10 | /* NEW.created_at = (case when TG_OP = 'INSERT' then NOW() else OLD.created_at end); */ 11 | /* NEW.updated_at = (case when TG_OP = 'UPDATE' and OLD.updated_at >= NOW() then OLD.updated_at + interval '1 millisecond' else NOW() end); */ 12 | /* return NEW; */ 13 | /* end; */ 14 | /* $$ language plpgsql volatile set search_path from current; */ 15 | 16 | /* comment on function app_private.tg__set_updated_at() is E'This trigger should be called on all tables with created_at, updated_at - it ensures that they cannot be manipulated and that updated_at will always be larger than the previous updated_at.'; */ 17 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/posts/priviligies/app_admin.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ :: name 3 | \set table $$'posts'$$ :: name 4 | \set role $$'app_admin'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY[ 15 | 'SELECT', 16 | 'INSERT', 17 | 'UPDATE', 18 | 'DELETE', 19 | 'REFERENCES', 20 | 'TRIGGER', 21 | 'TRUNCATE' 22 | ] 23 | ); 24 | 25 | SELECT any_column_privs_are( 26 | :schema, 27 | :table, 28 | :role, 29 | ARRAY[ 30 | 'SELECT', 31 | 'UPDATE', 32 | 'INSERT', 33 | 'REFERENCES' 34 | ] 35 | ); 36 | 37 | SELECT column_privs_are(:schema, :table, col, :role, ARRAY['SELECT', 'UPDATE', 'INSERT', 'REFERENCES']) 38 | FROM ( 39 | VALUES('id'), ('name'), ('user_id'), ('content'), ('created_at'), ('updated_at') 40 | ) AS tableAlias (col); 41 | 42 | 43 | select finish(); 44 | 45 | rollback; 46 | -------------------------------------------------------------------------------- /packages/db-tests/tests/queries/user_by_username_or_verified_email/success_by_username.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_anonymous'$$ 2 | \set user_secret_id $$'00000000-0000-0000-0000-000000000001'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set email $$'user@mail.com'$$ 5 | \set username $$'username2'$$ 6 | 7 | begin; 8 | 9 | select no_plan(); 10 | 11 | INSERT INTO app_private.user_secrets (id) 12 | VALUES (:user_secret_id); 13 | 14 | INSERT INTO app_public.users (id, user_secret_id, username) 15 | VALUES (:user_id, :user_secret_id, :username); 16 | 17 | set local role :role; 18 | select is(current_user, :role); 19 | 20 | prepare actual as 21 | select id, username 22 | from app_public.user_by_username_or_verified_email(:username); 23 | 24 | prepare expected as values 25 | (:user_id::uuid, :username); 26 | 27 | select set_eq( 28 | 'actual', 29 | 'expected' 30 | ); 31 | 32 | select finish(); 33 | 34 | rollback; 35 | -------------------------------------------------------------------------------- /packages/src/Webpack/FFI.purs: -------------------------------------------------------------------------------- 1 | module Webpack.FFI where 2 | 3 | import Effect.Uncurried (EffectFn1, EffectFn3) 4 | import Protolude 5 | import Webpack.Types (Assets, Compilation, RawSource, WebpackEntrypont) 6 | import Foreign (Foreign) 7 | import Foreign.JsMap (JsMap) 8 | import Node.Buffer (Buffer) 9 | import Unsafe.Coerce (unsafeCoerce) 10 | 11 | foreign import webpackEntrypontName :: EffectFn1 WebpackEntrypont String 12 | 13 | foreign import webpackEntrypontGetFiles :: EffectFn1 WebpackEntrypont (Array String) 14 | 15 | foreign import _rawSource :: Foreign -> RawSource 16 | 17 | rawSourceFromString :: String -> RawSource 18 | rawSourceFromString = _rawSource <<< unsafeCoerce 19 | 20 | rawSourceFromBuffer :: Buffer -> RawSource 21 | rawSourceFromBuffer = _rawSource <<< unsafeCoerce 22 | 23 | foreign import setAsset :: EffectFn3 Assets String RawSource Unit 24 | 25 | foreign import compilationGetEntrypoints :: EffectFn1 Compilation (JsMap String WebpackEntrypont) 26 | -------------------------------------------------------------------------------- /docker/common/migrator.nix: -------------------------------------------------------------------------------- 1 | { pkgs, rootProjectDir }: 2 | 3 | { 4 | useHostStore = true; 5 | 6 | entrypoint = ""; # I hate people that use custom entrypoints 7 | 8 | command = "${pkgs.coreutils}/bin/true"; # do nothing on `docker-compose up` 9 | 10 | working_dir = "/migrations"; 11 | 12 | environment = { 13 | TYPE = "postgresql"; 14 | LOGIN = "app_admin"; 15 | PASSWORD = "app_admin_pass"; 16 | HOST = "postgres"; 17 | PORT = 5432; 18 | DATABASE = "nextjsdemo_test"; 19 | 20 | PATH = let 21 | env = pkgs.buildEnv { 22 | name = "system-path"; 23 | paths = with pkgs; [ 24 | waitforit 25 | wait-for-postgres 26 | shmig 27 | ]; 28 | }; 29 | in "${env}/bin:/bin:/run/system/bin"; 30 | }; 31 | 32 | volumes = [ 33 | "${rootProjectDir}/migrations:/migrations:ro" 34 | "/tmp" # for shmig 35 | ]; 36 | 37 | depends_on = [ 38 | "postgres" 39 | ]; 40 | } 41 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/web_login/success_by_username_when_password_is_valid.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_owner'$$ 2 | \set user_secret_id $$'00000000-0000-0000-0000-000000000001'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set email $$'user@mail.com'$$ 5 | \set username $$'username2'$$ 6 | \set password $$'secret_pass'$$ 7 | 8 | begin; 9 | 10 | select no_plan(); 11 | 12 | INSERT INTO app_private.user_secrets (id, password_hash) 13 | VALUES (:user_secret_id, crypt(:password, gen_salt('bf'))); 14 | 15 | INSERT INTO app_public.users (id, user_secret_id, username) 16 | VALUES (:user_id, :user_secret_id, :username); 17 | 18 | set local role :role; 19 | select is(current_user, :role); 20 | 21 | prepare actual as 22 | select id from app_private.web_login(:username, :password); 23 | 24 | prepare expected as values 25 | (:user_id::uuid); 26 | 27 | select set_eq( 28 | 'actual', 29 | 'expected' 30 | ); 31 | 32 | select finish(); 33 | 34 | rollback; 35 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/really_create_user/success_not_verified.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_owner'$$ 2 | \set email $$'user@mail.com'$$ 3 | \set username $$'username2'$$ 4 | \set password $$'secret_pass'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | set local role :role; 11 | select is(current_user, :role); 12 | 13 | prepare actual as 14 | select username from app_private.really_create_user( 15 | username => :username, 16 | email => :email, 17 | email_is_verified => false, 18 | name => null, 19 | avatar_url => null, 20 | password => :password 21 | ); 22 | 23 | prepare expected as values 24 | (:username); 25 | 26 | select set_eq( 27 | 'actual', 28 | 'expected' 29 | ); 30 | 31 | set local role 'app_admin'; 32 | 33 | select set_eq( 34 | 'select task_identifier from "graphile_worker".jobs', 35 | ARRAY['JOB__SEND_VERIFICATION_EMAIL_FOR_USER_EMAIL'] 36 | ); 37 | 38 | select finish(); 39 | 40 | rollback; 41 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/update/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set role $$'app_owner'$$ 3 | \set user1_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set user2_id $$'00000000-0000-0000-0000-000000000002'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | insert into app_public.users 11 | (id, email, first_name, last_name) 12 | values 13 | (:user1_id, 'user@mail.com', random_string(), random_string()), 14 | (:user2_id, 'user2@mail.com', random_string(), random_string()); 15 | 16 | set local role :role; 17 | set local jwt.claims.user_id to :user1_id; 18 | select is(current_user, :role); 19 | 20 | update app_public.users set email = 'new_user@mail.com' where id = :user1_id; 21 | update app_public.users set email = 'new_user2@mail.com' where id = :user2_id; 22 | 23 | -- cant update another user 24 | prepare expected as values ('new_user@mail.com'), ('user2@mail.com'); 25 | 26 | select set_eq( 27 | 'select email from app_public.users', 28 | 'expected' 29 | ); 30 | 31 | select finish(); 32 | 33 | rollback; 34 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/send_reset_password/error_when_email_not_exists.sql_: -------------------------------------------------------------------------------- 1 | 2 | 3 | \set role $$'app_owner'$$ 4 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 5 | \set email $$'user@mail.com'$$ 6 | \set password $$'secret_pass'$$ 7 | 8 | begin; 9 | 10 | select no_plan(); 11 | 12 | select pgtest.spy('app_private', 'rabbitmq__send_reset_password_mail', ARRAY['app_public.users']); 13 | 14 | -- Test body 15 | set local role :role; 16 | select is(current_user, :role); 17 | 18 | prepare do_stuff as 19 | select app_public.send_reset_password(:email); 20 | 21 | select throws_ok( 22 | 'do_stuff', 23 | 'APP_EXCEPTION__SEND_RESET_PASSWORD__EMAIL_NOT_REGISTERED' 24 | ); 25 | 26 | -- Test body END 27 | 28 | -- Check app_private.rabbitmq__send_reset_password has not been called 29 | 30 | set local role 'app_owner'; 31 | 32 | select pgtest.assert_called(pgtest.get_mock_id('app_private', 'rabbitmq__send_reset_password_mail', ARRAY['app_public.users']), 0); 33 | 34 | select finish(); 35 | 36 | rollback; 37 | -------------------------------------------------------------------------------- /dev-commands/dev/lib.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | 3 | rec { 4 | config = 5 | { 6 | POSTGRES_HOST = "0.0.0.0"; 7 | POSTGRES_PORT = 5432; 8 | 9 | SERVER_HOST = "0.0.0.0"; 10 | SERVER_PORT = 5000; 11 | 12 | CLIENT_HOST = "0.0.0.0"; 13 | CLIENT_PORT = 3000; 14 | }; 15 | 16 | migratorEnv = 17 | { 18 | inherit (import "${pkgs.rootProjectDir}/config/public/database.nix") 19 | DATABASE_NAME; 20 | 21 | inherit (import "${pkgs.rootProjectDir}/config/ignored/passwords.nix") 22 | DATABASE_OWNER_PASSWORD; 23 | 24 | inherit (config) 25 | POSTGRES_HOST 26 | POSTGRES_PORT; 27 | }; 28 | 29 | serverEnv = 30 | { 31 | inherit (import "${pkgs.rootProjectDir}/config/public/database.nix") 32 | DATABASE_NAME; 33 | 34 | inherit (import "${pkgs.rootProjectDir}/config/ignored/passwords.nix") 35 | DATABASE_OWNER_PASSWORD; 36 | 37 | inherit (config) 38 | POSTGRES_HOST 39 | POSTGRES_PORT; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /nix/pkgs/gitCryptUnlock/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | inherit (pkgs) stdenv git-crypt git; 5 | in 6 | 7 | key: 8 | src: 9 | stdenv.mkDerivation { 10 | name = "decrypted-source"; 11 | inherit src; 12 | buildInputs = [ git-crypt git ]; 13 | phases = [ "installPhase" ]; 14 | installPhase = '' 15 | mkdir -p $out 16 | 17 | # copy content of folder with hidden files to $out 18 | cp -r $src/. $out 19 | 20 | cd $out 21 | 22 | # git crypt requires git 23 | git init 24 | 25 | # set only for this repo 26 | git config user.email "you@example.com" 27 | git config user.name "Your Name" 28 | 29 | git add --all 30 | git commit --quiet -m "dummy" 31 | 32 | # prevent "error: unable to unlink old... permission denied" 33 | # see https://stackoverflow.com/questions/11774397/git-push-error-unable-to-unlink-old-permission-denied/11774432 34 | chmod -R +w $out 35 | 36 | git-crypt unlock ${key} 37 | 38 | # don't leave traces 39 | rm -rfd .git 40 | ''; 41 | } 42 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Subscription.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Subscription where 2 | 3 | import Type.Row (type (+)) 4 | import GraphQLClient 5 | ( SelectionSet 6 | , Scope__RootSubscription 7 | , selectionForCompositeField 8 | , toGraphQLArguments 9 | , graphqlDefaultResponseFunctorOrScalarDecoderTransformer 10 | ) 11 | import NextjsGraphqlApi.Scopes (Scope__ListenPayload) 12 | 13 | type ListenInputRowRequired r = ( topic :: String | r ) 14 | 15 | type ListenInput = { | ListenInputRowRequired + () } 16 | 17 | listen :: forall r . ListenInput -> SelectionSet 18 | Scope__ListenPayload 19 | r -> SelectionSet 20 | Scope__RootSubscription 21 | r 22 | listen input = selectionForCompositeField 23 | "listen" 24 | (toGraphQLArguments 25 | input) 26 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 27 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users_oauths/priviliges/app_user.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set table $$'user_oauths'$$ 4 | \set role $$'app_user'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY[]::text[] 15 | ); 16 | 17 | SELECT any_column_privs_are( 18 | :schema, 19 | :table, 20 | :role, 21 | ARRAY[]::text[] 22 | ); 23 | 24 | SELECT column_privs_are(:schema, :table, 'id', :role, ARRAY[]::text[]); 25 | SELECT column_privs_are(:schema, :table, 'user_id', :role, ARRAY[]::text[]); 26 | SELECT column_privs_are(:schema, :table, 'service', :role, ARRAY[]::text[]); 27 | SELECT column_privs_are(:schema, :table, 'service_identifier', :role, ARRAY[]::text[]); 28 | SELECT column_privs_are(:schema, :table, 'created_at', :role, ARRAY[]::text[]); 29 | SELECT column_privs_are(:schema, :table, 'updated_at', :role, ARRAY[]::text[]); 30 | 31 | select finish(); 32 | 33 | rollback; 34 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Object/UserAuthenticationsEdge.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Object.UserAuthenticationsEdge where 2 | 3 | import GraphQLClient 4 | ( SelectionSet 5 | , selectionForField 6 | , graphqlDefaultResponseScalarDecoder 7 | , selectionForCompositeField 8 | , graphqlDefaultResponseFunctorOrScalarDecoderTransformer 9 | ) 10 | import NextjsGraphqlApi.Scopes 11 | (Scope__UserAuthenticationsEdge, Scope__UserAuthentication) 12 | import Data.Maybe (Maybe) 13 | import NextjsGraphqlApi.Scalars (Cursor) 14 | 15 | cursor :: SelectionSet Scope__UserAuthenticationsEdge (Maybe Cursor) 16 | cursor = selectionForField "cursor" [] graphqlDefaultResponseScalarDecoder 17 | 18 | node :: forall r . SelectionSet 19 | Scope__UserAuthentication 20 | r -> SelectionSet 21 | Scope__UserAuthenticationsEdge 22 | r 23 | node = selectionForCompositeField 24 | "node" 25 | [] 26 | graphqlDefaultResponseFunctorOrScalarDecoderTransformer 27 | -------------------------------------------------------------------------------- /packages/api-server/ApiServer/FrontendServerHttpProxy.js_: -------------------------------------------------------------------------------- 1 | // from https://github.com/graphile/bootstrap-react-apollo/blob/master/server/middleware/installClientServerProxy.js 2 | 3 | const httpProxy = require("http-proxy") 4 | 5 | exports._installFrontendServerProxy = function installFrontendServer(httpServer, app, targetUrl) { 6 | const proxy = httpProxy.createProxyServer({ 7 | target: targetUrl, 8 | ws: true, 9 | }) 10 | 11 | proxy.on('error', e => { 12 | console.error("Proxy error occurred:", e) 13 | }) 14 | 15 | app.use((req, res, _next) => { 16 | proxy.web(req, res, {}, e => { 17 | console.error(e) 18 | 19 | res.statusCode = 503 20 | 21 | res.end("Error occurred while proxying to client application - is it running? " + e.message) 22 | }) 23 | }) 24 | 25 | // DONT PROXY WEBSOCKET ON DEV TO CLIENT SSR SERVER 26 | // because Postgraphile of this api server will handle it 27 | // httpServer.on("upgrade", (req, socket, head) => { 28 | // proxy.ws(req, socket, head) 29 | // }) 30 | } 31 | -------------------------------------------------------------------------------- /packages/worker/Worker/Config/FromEnv.purs: -------------------------------------------------------------------------------- 1 | module Worker.Config.FromEnv where 2 | 3 | import Protolude 4 | 5 | import Data.NonEmpty (NonEmpty(..)) 6 | import Data.String.NonEmpty (NonEmptyString) 7 | import Data.String.NonEmpty as NonEmptyString 8 | import Env as Env 9 | import Foreign.Object (Object) 10 | 11 | type EnvConfig = 12 | { nodemailerRealPass :: Maybe String 13 | , nodemailerRealUser :: Maybe String 14 | , databaseOwnerPassword :: String 15 | } 16 | 17 | envConfig :: Effect EnvConfig 18 | envConfig = Env.parse Env.defaultInfo $ ado 19 | nodemailerRealPass <- Env.optionalVar Env.nonempty "nodemailerRealPass" (Env.defaultOptionalVarOptions { sensitive = true }) 20 | nodemailerRealUser <- Env.optionalVar Env.nonempty "nodemailerRealUser" (Env.defaultOptionalVarOptions { sensitive = true }) 21 | databaseOwnerPassword <- Env.var Env.nonempty "databaseOwnerPassword" (Env.defaultVarOptions { sensitive = true }) 22 | 23 | in 24 | { nodemailerRealPass 25 | , nodemailerRealUser 26 | , databaseOwnerPassword 27 | } 28 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Pages/Examples/Ace.deps.js: -------------------------------------------------------------------------------- 1 | // FROM https://github.com/ajaxorg/ace-builds/issues/129#issuecomment-406783352 2 | 3 | import 'ace-builds/src-min-noconflict/ace' // Load Ace Editor 4 | 5 | // Import initial theme and mode so we don't have to wait for 2 extra HTTP requests 6 | import 'ace-builds/src-min-noconflict/theme-chrome' 7 | import 'ace-builds/src-min-noconflict/mode-javascript' 8 | 9 | // cdnjs didn't have a "no-conflict" version, so we'll use jsdelivr 10 | const CDN = 'https://cdn.jsdelivr.net/npm/ace-builds@1.3.3/src-min-noconflict'; 11 | 12 | // Now we tell ace to use the CDN locations to look for files 13 | ace.config.set('basePath', CDN); 14 | ace.config.set('modePath', CDN); 15 | ace.config.set('themePath', CDN); 16 | ace.config.set('workerPath', CDN); 17 | 18 | // // Create Editor 19 | // const editor = ace.edit('editor'); 20 | 21 | // // Set Editor Theme and Mode 22 | // editor.setTheme('ace/theme/chrome'); 23 | // editor.session.setMode('ace/mode/javascript'); 24 | 25 | ///////////////// 26 | 27 | import './Ace.scss' 28 | -------------------------------------------------------------------------------- /packages/db-tests/tests/queries/user_by_username_or_verified_email/success_by_email.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_anonymous'$$ 2 | \set user_secret_id $$'00000000-0000-0000-0000-000000000001'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set email $$'user@mail.com'$$ 5 | \set username $$'username2'$$ 6 | 7 | begin; 8 | 9 | select no_plan(); 10 | 11 | INSERT INTO app_private.user_secrets (id) 12 | VALUES (:user_secret_id); 13 | 14 | INSERT INTO app_public.users (id, user_secret_id, username) 15 | VALUES (:user_id, :user_secret_id, :username); 16 | 17 | INSERT INTO app_hidden.user_emails (user_id, email, is_verified) 18 | VALUES (:user_id, :email, true); 19 | 20 | set local role :role; 21 | select is(current_user, :role); 22 | 23 | prepare actual as 24 | select id, username 25 | from app_public.user_by_username_or_verified_email(:email); 26 | 27 | prepare expected as values 28 | (:user_id::uuid, :username); 29 | 30 | select set_eq( 31 | 'actual', 32 | 'expected' 33 | ); 34 | 35 | select finish(); 36 | 37 | rollback; 38 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/components-inputs/src/Display.purs: -------------------------------------------------------------------------------- 1 | module Example.Components.Inputs.Display where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Halogen as H 7 | import Halogen.HTML as HH 8 | 9 | type Slot p = forall q. H.Slot q Void p 10 | 11 | type Input = Int 12 | 13 | type State = Int 14 | 15 | data Action = HandleInput Int 16 | 17 | component :: forall q o m. H.Component q Input o m 18 | component = 19 | H.mkComponent 20 | { initialState: identity 21 | , render 22 | , eval: H.mkEval $ H.defaultEval 23 | { handleAction = handleAction 24 | , receive = Just <<< HandleInput 25 | } 26 | } 27 | 28 | render :: forall m. State -> H.ComponentHTML Action () m 29 | render state = 30 | HH.div_ 31 | [ HH.text "My input value is:" 32 | , HH.strong_ [ HH.text (show state) ] 33 | ] 34 | 35 | handleAction :: forall o m. Action -> H.HalogenM State Action () o m Unit 36 | handleAction = case _ of 37 | HandleInput n -> do 38 | oldN <- H.get 39 | when (oldN /= n) $ H.put n 40 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Data/InUseUsernameOrEmail.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Data.InUseUsernameOrEmail where 2 | 3 | import Protolude 4 | 5 | import Data.String.NonEmpty (NonEmptyString) 6 | import Data.String.NonEmpty as NonEmptyString 7 | import NextjsApp.Queries.IsUsernameOrEmailInUse (isUsernameOrEmailInUse) 8 | 9 | data InUseUsernameOrEmail__Error 10 | = InUseUsernameOrEmail__Error__Empty 11 | | InUseUsernameOrEmail__Error__NotInUse 12 | 13 | newtype InUseUsernameOrEmail = InUseUsernameOrEmail NonEmptyString 14 | 15 | toString :: InUseUsernameOrEmail -> String 16 | toString (InUseUsernameOrEmail s) = NonEmptyString.toString s 17 | 18 | fromString :: String -> Aff (Either InUseUsernameOrEmail__Error InUseUsernameOrEmail) 19 | fromString = NonEmptyString.fromString >>> 20 | case _ of 21 | Nothing -> pure $ Left InUseUsernameOrEmail__Error__Empty 22 | Just usernameOrEmail -> isUsernameOrEmailInUse usernameOrEmail <#> 23 | if _ 24 | then Right (InUseUsernameOrEmail usernameOrEmail) 25 | else Left InUseUsernameOrEmail__Error__NotInUse 26 | -------------------------------------------------------------------------------- /packages/src/FRPEventExtra.purs: -------------------------------------------------------------------------------- 1 | module FRPEventExtra where 2 | 3 | import Protolude 4 | import FRP.Event (Event) 5 | import FRP.Event as Event 6 | import Effect.Ref as Ref 7 | 8 | withPrevious :: forall a. Event a -> Event { previous :: Maybe a, current :: a } 9 | withPrevious event = 10 | Event.makeEvent \push -> do 11 | latest <- Ref.new Nothing 12 | closeInner <- 13 | Event.subscribe event \current -> do 14 | previous <- Ref.read latest 15 | Ref.write (Just current) latest 16 | push { previous, current } 17 | pure closeInner 18 | 19 | distinctWith :: forall a. (a -> a -> Boolean) -> Event a -> Event a 20 | distinctWith isEq event = 21 | Event.filterMap 22 | ( \{ current, previous } -> case previous of 23 | Just previous' -> 24 | if isEq previous' current then 25 | Nothing 26 | else 27 | Just current 28 | Nothing -> Just current 29 | ) 30 | (withPrevious event) 31 | 32 | distinct :: forall a. Eq a => Event a -> Event a 33 | distinct = distinctWith eq 34 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/PageImplementations/Secret.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.PageImplementations.Secret (component) where 2 | 3 | import NextjsApp.PageImplementations.Secret.Types 4 | import Protolude 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | import NextjsApp.AppM (AppM) 8 | import NextjsApp.Route as NextjsApp.Route 9 | 10 | type Query 11 | = Const Void 12 | 13 | type Input 14 | = SecretPageUserData 15 | 16 | type Message 17 | = Void 18 | 19 | type State 20 | = SecretPageUserData 21 | 22 | type Action 23 | = Void 24 | 25 | type ChildSlots 26 | = () 27 | 28 | component :: H.Component Query Input Message AppM 29 | component = 30 | H.mkComponent 31 | { initialState: identity 32 | , render 33 | , eval: H.mkEval $ H.defaultEval 34 | } 35 | 36 | render 37 | :: forall (slots :: Row Type) routes 38 | . State 39 | -> HH.ComponentHTML 40 | Action 41 | (slots :: Row Type) 42 | (AppM routes) 43 | render (SecretPageUserData input) = 44 | HH.div_ 45 | [ HH.text "You are on secret page" 46 | , HH.text $ show input 47 | ] 48 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users/priviliges/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set table $$'users'$$ 4 | \set role $$'app_owner'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY[ 15 | 'SELECT', 16 | 'DELETE', 17 | 'UPDATE' 18 | ] 19 | ); 20 | 21 | -- TODO: what is this? 22 | SELECT any_column_privs_are( 23 | :schema, 24 | :table, 25 | :role, 26 | ARRAY[ 27 | 'SELECT', 28 | 'UPDATE' 29 | ] 30 | ); 31 | 32 | -- TODO: what is this too? 33 | SELECT column_privs_are(:schema, :table, 'id', :role, ARRAY['SELECT', 'UPDATE']); 34 | SELECT column_privs_are(:schema, :table, 'first_name', :role, ARRAY['SELECT', 'UPDATE']); 35 | SELECT column_privs_are(:schema, :table, 'last_name', :role, ARRAY['SELECT', 'UPDATE']); 36 | SELECT column_privs_are(:schema, :table, 'created_at', :role, ARRAY['SELECT', 'UPDATE']); 37 | SELECT column_privs_are(:schema, :table, 'updated_at', :role, ARRAY['SELECT', 'UPDATE']); 38 | 39 | select finish(); 40 | 41 | rollback; 42 | -------------------------------------------------------------------------------- /packages/client/NextjsApp/Link/Lib.purs: -------------------------------------------------------------------------------- 1 | module NextjsApp.Link.Lib where 2 | 3 | import Protolude 4 | 5 | import Control.Monad.Reader.Class (class MonadAsk) 6 | import Control.Monad.State (class MonadState) 7 | import Effect.Class (class MonadEffect) 8 | import Halogen as H 9 | import NextjsApp.Navigate as NextjsApp.Navigate 10 | import Web.Event.Event as Web.Event.Event 11 | import Web.UIEvent.MouseEvent (MouseEvent) 12 | import Web.UIEvent.MouseEvent as Web.UIEvent.MouseEvent 13 | 14 | elementLabel :: H.RefLabel 15 | elementLabel = H.RefLabel "link" 16 | 17 | handleMouseEvent :: ∀ t16 t17 t19 t4. Bind t4 ⇒ MonadEffect t4 ⇒ MonadState { route :: Variant t17 | t16 } t4 ⇒ MonadAsk { navigate :: Variant t17 -> Effect Unit | t19 } t4 ⇒ MouseEvent → t4 Unit 18 | handleMouseEvent mouseEvent = do 19 | -- TODO: ignore newtab clicks https://github.com/vercel/next.js/blob/8dd3d2a8e2b266611a60b9550d2ecac02f14fd57/packages/next/client/link.tsx#L171-L182 20 | H.liftEffect $ Web.Event.Event.preventDefault (Web.UIEvent.MouseEvent.toEvent mouseEvent) 21 | 22 | H.gets _.route >>= NextjsApp.Navigate.navigate 23 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/web_login/success_by_email_when_password_is_valid.sql: -------------------------------------------------------------------------------- 1 | \set role $$'app_owner'$$ 2 | \set user_secret_id $$'00000000-0000-0000-0000-000000000001'$$ 3 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 4 | \set email $$'user@mail.com'$$ 5 | \set username $$'username2'$$ 6 | \set password $$'secret_pass'$$ 7 | 8 | begin; 9 | 10 | select no_plan(); 11 | 12 | INSERT INTO app_private.user_secrets (id, password_hash) 13 | VALUES (:user_secret_id, crypt(:password, gen_salt('bf'))); 14 | 15 | INSERT INTO app_public.users (id, user_secret_id, username) 16 | VALUES (:user_id, :user_secret_id, :username); 17 | 18 | INSERT INTO app_hidden.user_emails (user_id, email, is_verified) 19 | VALUES (:user_id, :email, true); 20 | 21 | set local role :role; 22 | select is(current_user, :role); 23 | 24 | prepare actual as 25 | select id from app_private.web_login(:email, :password); 26 | 27 | prepare expected as values 28 | (:user_id::uuid); 29 | 30 | select set_eq( 31 | 'actual', 32 | 'expected' 33 | ); 34 | 35 | select finish(); 36 | 37 | rollback; 38 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/A.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.A (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | import Example.DeeplyNested.B as B 8 | import Example.DeeplyNested.C as C 9 | import Data.Const (Const) 10 | import Type.Proxy (Proxy(..)) 11 | 12 | type State = Unit 13 | 14 | data Action = Void 15 | 16 | type ChildSlots = 17 | ( b :: H.Slot (Const Void) Void Unit 18 | , c :: H.Slot (Const Void) Void Unit 19 | ) 20 | 21 | _b :: Proxy "b" 22 | _b = Proxy 23 | 24 | _c :: Proxy "c" 25 | _c = Proxy 26 | 27 | component :: forall q i o m. H.Component q i o m 28 | component = 29 | H.mkComponent 30 | { initialState: const unit 31 | , render 32 | , eval: H.mkEval H.defaultEval 33 | } 34 | 35 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 36 | render state = 37 | HH.ul_ 38 | [ HH.li_ [ HH.text "a" ] 39 | , HH.li_ [ HH.slot _b unit B.component unit absurd ] 40 | , HH.li_ [ HH.slot _c unit C.component unit absurd ] 41 | , HH.li_ [ HH.text "a end" ] 42 | ] 43 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/deeply-nested/src/B.purs: -------------------------------------------------------------------------------- 1 | module Example.DeeplyNested.B (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | import Example.DeeplyNested.D as D 8 | import Example.DeeplyNested.E as E 9 | import Data.Const (Const) 10 | import Type.Proxy (Proxy(..)) 11 | 12 | type State = Unit 13 | 14 | data Action = Void 15 | 16 | type ChildSlots = 17 | ( d :: H.Slot (Const Void) Void Unit 18 | , e :: H.Slot (Const Void) Void Unit 19 | ) 20 | 21 | _d :: Proxy "d" 22 | _d = Proxy 23 | 24 | _e :: Proxy "e" 25 | _e = Proxy 26 | 27 | component :: forall q i o m. H.Component q i o m 28 | component = 29 | H.mkComponent 30 | { initialState: const unit 31 | , render 32 | , eval: H.mkEval H.defaultEval 33 | } 34 | 35 | render :: forall m. State -> H.ComponentHTML Action ChildSlots m 36 | render state = 37 | HH.ul_ 38 | [ HH.li_ [ HH.text "b" ] 39 | , HH.li_ [ HH.slot _d unit D.component unit absurd ] 40 | , HH.li_ [ HH.slot _e unit E.component unit absurd ] 41 | , HH.li_ [ HH.text "b end" ] 42 | ] 43 | -------------------------------------------------------------------------------- /nix/pkgs/wait-for-postgres/wait-for-postgres: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # idea stolen from https://gist.github.com/nicerobot/1136dcfba6ce3da67ce3ded5101a4078 4 | 5 | # USAGE 6 | # ./wait-for-postgres postgres://$LOGIN:$PASSWORD@$HOST:$PORT/$DATABASE 7 | 8 | # Retries a command on failure (idea stolen from http://fahdshariff.blogspot.com/2014/02/retrying-commands-in-shell-scripts.html). 9 | # $1 - the max number of attempts 10 | # $2 - the seconds to sleep 11 | # $3... - the command to run 12 | retry() { 13 | max_attempts="$1"; shift 14 | seconds="$1"; shift 15 | cmd="$@" 16 | attempt_num=1 17 | 18 | until $cmd 19 | do 20 | if [ $attempt_num -eq $max_attempts ] 21 | then 22 | echo "Attempt $attempt_num failed and there are no more attempts left!" 23 | return 1 24 | else 25 | echo "Attempt $attempt_num failed! Trying again in $seconds seconds..." 26 | attempt_num=`expr "$attempt_num" + 1` 27 | sleep "$seconds" 28 | fi 29 | done 30 | } 31 | 32 | retry 5 1 psql -c '\l' ${@} >/dev/null 33 | 34 | echo >&2 "$(date +%Y%m%dt%H%M%S) Postgres is up - executing command" 35 | -------------------------------------------------------------------------------- /packages/client-halogen-examples/basic/src/Button.purs: -------------------------------------------------------------------------------- 1 | module Example.Basic.Button (component) where 2 | 3 | import Prelude 4 | 5 | import Halogen as H 6 | import Halogen.HTML as HH 7 | import Halogen.HTML.Events as HE 8 | import Halogen.HTML.Properties as HP 9 | 10 | type State = { enabled :: Boolean } 11 | 12 | data Action = Toggle 13 | 14 | component :: forall q i o m. H.Component q i o m 15 | component = 16 | H.mkComponent 17 | { initialState 18 | , render 19 | , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } 20 | } 21 | 22 | initialState :: forall i. i -> State 23 | initialState _ = { enabled: false } 24 | 25 | render :: forall m. State -> H.ComponentHTML Action () m 26 | render state = 27 | let 28 | label = if state.enabled then "On" else "Off" 29 | in 30 | HH.button 31 | [ HP.title label 32 | , HE.onClick \_ -> Toggle 33 | ] 34 | [ HH.text label ] 35 | 36 | handleAction ∷ forall o m. Action → H.HalogenM State Action () o m Unit 37 | handleAction = case _ of 38 | Toggle -> 39 | H.modify_ \st -> st { enabled = not st.enabled } 40 | -------------------------------------------------------------------------------- /packages/db-tests/tests/schemas/app_private.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_private'$$ 3 | 4 | begin; 5 | 6 | select no_plan(); 7 | 8 | select tables_are( 9 | :schema, 10 | array[ 11 | 'user_authentication_secrets', 12 | 'user_secrets', 13 | 'user_sessions' 14 | ]::text[] 15 | ); 16 | 17 | select types_are(:schema, array[]::text[]); 18 | 19 | select domains_are(:schema, array[]::text[]); 20 | 21 | select enums_are(:schema, array[]::text[]); 22 | 23 | select functions_are( 24 | :schema, 25 | array[ 26 | 'tg__set_updated_at', 27 | 'link_or_register_user', 28 | 'web_login', 29 | 'really_create_user', 30 | 'register_user' 31 | /* 'tg_send_verification_email_for_user_email', */ 32 | /* 'tg_user_email_secrets__insert_with_user_email', */ 33 | /* 'tg_user_secrets__insert_with_user', */ 34 | /* 'tg_users__make_first_user_admin' */ 35 | ]::text[] 36 | ); 37 | 38 | select views_are( 39 | :schema, 40 | array[]::text[] 41 | ); 42 | 43 | select materialized_views_are( 44 | :schema, 45 | array[]::text[] 46 | ); 47 | 48 | select finish(); 49 | 50 | rollback; 51 | -------------------------------------------------------------------------------- /packages/db-tests/tests-todo/tables/users_oauths/priviliges/app_owner.sql: -------------------------------------------------------------------------------- 1 | 2 | \set schema $$'app_public'$$ 3 | \set table $$'user_oauths'$$ 4 | \set role $$'app_owner'$$ 5 | 6 | begin; 7 | 8 | select no_plan(); 9 | 10 | SELECT table_privs_are( 11 | :schema, 12 | :table, 13 | :role, 14 | ARRAY[ 15 | 'SELECT', 16 | 'DELETE' 17 | ] 18 | ); 19 | 20 | -- TODO: what is this? 21 | SELECT any_column_privs_are( 22 | :schema, 23 | :table, 24 | :role, 25 | ARRAY[ 26 | 'SELECT' 27 | ] 28 | ); 29 | 30 | SELECT column_privs_are(:schema, :table, 'id', :role, ARRAY['SELECT']); 31 | SELECT column_privs_are(:schema, :table, 'user_id', :role, ARRAY['SELECT']); 32 | SELECT column_privs_are(:schema, :table, 'service', :role, ARRAY['SELECT']); 33 | SELECT column_privs_are(:schema, :table, 'service_identifier', :role, ARRAY['SELECT']); 34 | SELECT column_privs_are(:schema, :table, 'created_at', :role, ARRAY['SELECT']); 35 | SELECT column_privs_are(:schema, :table, 'updated_at', :role, ARRAY['SELECT']); 36 | 37 | select finish(); 38 | 39 | rollback; 40 | -------------------------------------------------------------------------------- /packages/client/NextjsGraphqlApi/Object/PageInfo.purs: -------------------------------------------------------------------------------- 1 | module NextjsGraphqlApi.Object.PageInfo where 2 | 3 | import GraphQLClient 4 | (SelectionSet, selectionForField, graphqlDefaultResponseScalarDecoder) 5 | import NextjsGraphqlApi.Scopes (Scope__PageInfo) 6 | import Data.Maybe (Maybe) 7 | import NextjsGraphqlApi.Scalars (Cursor) 8 | 9 | endCursor :: SelectionSet Scope__PageInfo (Maybe Cursor) 10 | endCursor = selectionForField "endCursor" [] graphqlDefaultResponseScalarDecoder 11 | 12 | hasNextPage :: SelectionSet Scope__PageInfo Boolean 13 | hasNextPage = selectionForField 14 | "hasNextPage" 15 | [] 16 | graphqlDefaultResponseScalarDecoder 17 | 18 | hasPreviousPage :: SelectionSet Scope__PageInfo Boolean 19 | hasPreviousPage = selectionForField 20 | "hasPreviousPage" 21 | [] 22 | graphqlDefaultResponseScalarDecoder 23 | 24 | startCursor :: SelectionSet Scope__PageInfo (Maybe Cursor) 25 | startCursor = selectionForField 26 | "startCursor" 27 | [] 28 | graphqlDefaultResponseScalarDecoder 29 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/reset_password/error_when_token_not_exists.sql_: -------------------------------------------------------------------------------- 1 | 2 | 3 | \set role $$'app_owner'$$ 4 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 5 | \set email $$'user@mail.com'$$ 6 | \set password $$'secret_pass'$$ 7 | \set token $$'some-token'$$ 8 | \set new_password $$'new_secret_pass'$$ 9 | 10 | begin; 11 | 12 | select no_plan(); 13 | 14 | select pgtest.spy('app_private', 'rabbitmq__send_password_was_changed_mail', ARRAY['app_public.users']); 15 | 16 | -- Test body 17 | set local role :role; 18 | select is(current_user, :role); 19 | 20 | prepare do_stuff as 21 | select app_public.reset_password(:token, :new_password); 22 | 23 | select throws_ok( 24 | 'do_stuff', 25 | 'APP_EXCEPTION__RESET_PASSWORD__TOKEN_IS_INVALID' 26 | ); 27 | 28 | -- Test body END 29 | 30 | -- Check app_private.rabbitmq__reset_password has not been called 31 | 32 | set local role 'app_owner'; 33 | 34 | select pgtest.assert_called(pgtest.get_mock_id('app_private', 'rabbitmq__send_password_was_changed_mail', ARRAY['app_public.users']), 0); 35 | 36 | select finish(); 37 | 38 | rollback; 39 | -------------------------------------------------------------------------------- /packages/client-webpack/NextjsWebpack/FaviconsConfig.purs: -------------------------------------------------------------------------------- 1 | module NextjsWebpack.FaviconsConfig where 2 | 3 | import Data.Nullable as Nullable 4 | import Favicons (FaviconsConfig) 5 | 6 | faviconsConfig :: Boolean -> FaviconsConfig 7 | faviconsConfig isProd = 8 | { path: "/" 9 | , appName: Nullable.notNull "Nextjs example app" 10 | , appShortName: Nullable.notNull "Nextjs" 11 | , appDescription: Nullable.notNull "Example application for the best framework" 12 | , developerName: Nullable.null 13 | , developerURL: Nullable.null 14 | , dir: "auto" 15 | , lang: "en-US" 16 | , background: "#fff" 17 | , theme_color: "#fff" 18 | , appleStatusBarStyle: "black-translucent" 19 | , display: "standalone" 20 | , orientation: "any" 21 | , scope: "/" 22 | , start_url: "/" 23 | , version: "1.0" 24 | , logging: false 25 | , pixel_art: false 26 | , loadManifestWithCredentials: false 27 | , icons: 28 | { android: isProd 29 | , appleIcon: false 30 | , appleStartup: false 31 | , coast: false 32 | , favicons: isProd 33 | , firefox: isProd 34 | , windows: false 35 | , yandex: false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/client-webpack/NextjsWebpack/WebpackConfig/SpagoOptions.purs: -------------------------------------------------------------------------------- 1 | module NextjsWebpack.WebpackConfig.SpagoOptions where 2 | 3 | import Protolude 4 | import Data.String as String 5 | import WebpackSpagoLoader (SpagoOptions, getAbsoluteOutputDirFromSpago, getSourcesFromSpago) 6 | import Effect.Uncurried (runEffectFn1) 7 | import Pathy (Abs, File, Path) 8 | import PathyExtra (printPathPosixSandboxAny) 9 | 10 | spagoOptions :: Path Abs File -> Effect SpagoOptions 11 | spagoOptions spagoDhall = do 12 | let 13 | spagoDhall' = printPathPosixSandboxAny spagoDhall 14 | output <- runEffectFn1 getAbsoluteOutputDirFromSpago spagoDhall' 15 | pursFiles <- runEffectFn1 getSourcesFromSpago spagoDhall' 16 | pure 17 | { output 18 | , pursFiles 19 | , compiler: "psa" 20 | -- note that warnings are shown only when file is recompiled, delete output folder to show all warnings 21 | , compilerOptions: 22 | { censorCodes: 23 | String.joinWith "," 24 | [ "ImplicitQualifiedImport" 25 | , "UnusedImport" 26 | , "ImplicitImport" 27 | ] 28 | -- strict: true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/db-tests/tests/actions/resend_confirmation/success.sql_: -------------------------------------------------------------------------------- 1 | 2 | 3 | \set role $$'app_owner'$$ 4 | \set user_id $$'00000000-0000-0000-0000-000000000001'$$ 5 | \set email $$'user@mail.com'$$ 6 | \set password $$'secret_pass'$$ 7 | 8 | begin; 9 | 10 | select no_plan(); 11 | 12 | insert into app_public.users 13 | (id, email, first_name, last_name, is_confirmed, password_hash) 14 | values 15 | (:user_id, :email, random_string(), random_string(), false, crypt(:password, gen_salt('bf'))); 16 | 17 | -- Spy 18 | 19 | select pgtest.spy('app_private', 'rabbitmq__send_confirmation_mail', ARRAY['app_public.users']); 20 | 21 | -- Spy END 22 | 23 | -- Test body 24 | set local role :role; 25 | select is(current_user, :role); 26 | 27 | prepare do_stuff as 28 | select app_public.resend_confirmation(:email); 29 | 30 | select lives_ok('do_stuff'); 31 | 32 | -- Test body END 33 | 34 | -- Check function has been called 35 | 36 | set local role 'app_owner'; 37 | 38 | select pgtest.assert_called(pgtest.get_mock_id('app_private', 'rabbitmq__send_confirmation_mail', ARRAY['app_public.users']), 1); 39 | 40 | select finish(); 41 | 42 | rollback; 43 | -------------------------------------------------------------------------------- /packages/src/SimpleXMLWithIndentation.purs: -------------------------------------------------------------------------------- 1 | module SimpleXMLWithIndentation where 2 | 3 | import Protolude 4 | import Data.String.Yarn (unlines) as String 5 | import Data.String.Common (joinWith) as String 6 | 7 | indent :: String -> String 8 | indent x = " " <> x 9 | 10 | unlinesIndent :: Array String -> String 11 | unlinesIndent = String.unlines <<< map indent 12 | 13 | tagStart :: String -> String 14 | tagStart x = "<" <> x <> ">" 15 | 16 | tagEnd :: String -> String 17 | tagEnd x = " x <> ">" 18 | 19 | printProp :: Tuple String String -> String 20 | printProp (name /\ val) = name <> "=\"" <> val <> "\"" 21 | 22 | printProps :: Array (Tuple String String) -> String 23 | printProps = String.joinWith " " <<< map printProp 24 | 25 | tagOneline :: String -> Array (String /\ String) -> String -> String 26 | tagOneline tagName props content = tagStart (tagName <> " " <> printProps props) <> content <> tagEnd tagName 27 | 28 | tagMultiLine :: String -> Array (String /\ String) -> Array String -> String 29 | tagMultiLine tagName props content = String.unlines $ [ tagStart (tagName <> " " <> printProps props), unlinesIndent content, tagEnd tagName ] 30 | --------------------------------------------------------------------------------