Migrate some code from todo example to prisma.

This commit is contained in:
Dillon Kearns 2023-01-30 14:21:06 -08:00
parent e6c372b14b
commit 114043eaee
6 changed files with 259 additions and 31 deletions

View File

@ -27,7 +27,6 @@ import Pages.Msg
import Pages.PageUrl exposing (PageUrl)
import Pages.Script as Script
import Pages.Url
import Request.Hasura
import Route
import RouteBuilder exposing (StatelessRoute, StaticPayload)
import SendGrid
@ -215,13 +214,15 @@ data routeParams =
(\emailIfValid ->
case maybeSessionId of
Just sessionId ->
Data.Session.get sessionId
|> Request.Hasura.backendTask
BackendTask.Custom.run
"getEmailBySessionId"
(Encode.string sessionId)
(Decode.maybe Decode.string)
|> BackendTask.allowFatal
|> BackendTask.map
(\maybeUserSession ->
( okSessionThing
, maybeUserSession
|> Maybe.map .emailAddress
|> Data
|> Server.Response.render
)
@ -230,24 +231,27 @@ data routeParams =
Nothing ->
case emailIfValid of
Just confirmedEmail ->
Data.Session.findOrCreateUser confirmedEmail
|> Request.Hasura.mutationBackendTask
BackendTask.Time.now
|> BackendTask.andThen
(\userId ->
BackendTask.Time.now
|> BackendTask.andThen
(\now_ ->
let
expirationTime : Time.Posix
expirationTime =
Time.millisToPosix (Time.posixToMillis now_ + (1000 * 60 * 30))
in
Data.Session.create expirationTime userId
|> Request.Hasura.mutationBackendTask
)
(\now_ ->
let
expirationTime : Time.Posix
expirationTime =
Time.millisToPosix (Time.posixToMillis now_ + (1000 * 60 * 30))
in
BackendTask.Custom.run "findOrCreateUserAndSession"
(Encode.object
[ ( "confirmedEmail"
, Encode.string confirmedEmail
)
, ( "expirationTime", expirationTime |> Time.posixToMillis |> Encode.int )
]
)
Decode.string
|> BackendTask.allowFatal
)
|> BackendTask.map
(\(Uuid sessionId) ->
(\sessionId ->
( okSessionThing
|> Session.insert "sessionId" sessionId
, Route.Visibility__ { visibility = Nothing }
@ -267,13 +271,19 @@ data routeParams =
Nothing ->
maybeSessionId
|> Maybe.map (Data.Session.get >> Request.Hasura.backendTask)
|> Maybe.map
(\sessionId ->
BackendTask.Custom.run
"getEmailBySessionId"
(Encode.string sessionId)
(Decode.maybe Decode.string)
)
|> Maybe.withDefault (BackendTask.succeed Nothing)
|> BackendTask.allowFatal
|> BackendTask.map
(\maybeUserSession ->
(\maybeEmail ->
( okSessionThing
, maybeUserSession
|> Maybe.map .emailAddress
, maybeEmail
|> Data
|> Server.Response.render
)

View File

@ -2,6 +2,7 @@ module Route.Visibility__ exposing (ActionData, Data, Model, Msg, route)
import Api.Scalar exposing (Uuid(..))
import BackendTask exposing (BackendTask)
import BackendTask.Custom
import Data.Session
import Data.Todo
import Dict exposing (Dict)
@ -18,6 +19,8 @@ import Html.Attributes exposing (..)
import Html.Keyed as Keyed
import Html.Lazy exposing (lazy, lazy2)
import Icon
import Json.Decode as Decode
import Json.Encode as Encode
import LoadingSpinner
import MySession
import Pages.Msg
@ -214,17 +217,15 @@ data routeParams =
(\parsedSession requestTime session ->
case visibilityFromRouteParams routeParams of
Just visibility ->
Data.Todo.findAllBySession parsedSession
|> Request.Hasura.backendTask
BackendTask.Custom.run "getTodosBySession"
(Encode.string parsedSession)
(Decode.list todoDecoder)
|> BackendTask.allowFatal
|> BackendTask.map
(\todos ->
( session
, Response.render
{ entries =
todos
-- TODO add error handling for Nothing case
|> Maybe.withDefault []
|> List.map toOptimisticTodo
{ entries = todos |> List.map toOptimisticTodo
, visibility = visibility
, requestTime = requestTime
}
@ -240,6 +241,21 @@ data routeParams =
)
type alias Todo =
{ description : String
, completed : Bool
, id : Uuid
}
todoDecoder : Decode.Decoder Todo
todoDecoder =
Decode.map3 Todo
(Decode.field "title" Decode.string)
(Decode.field "complete" Decode.bool)
(Decode.field "id" (Decode.string |> Decode.map Uuid))
action : RouteParams -> Request.Parser (BackendTask FatalError (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair

View File

@ -1,8 +1,83 @@
import kleur from "kleur";
import crypto from "crypto";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
kleur.enabled = true;
export async function users() {
const users = await prisma.user.findMany();
}
export async function getTodosBySession(sessionId) {
try {
return (
await prisma.session.findUnique({
where: { id: sessionId },
include: {
user: {
select: {
todos: {
select: {
complete: true,
// , createdAt: true
id: true,
title: true,
},
},
},
},
},
})
)?.user.todos;
} catch (e) {
console.trace(e);
return null;
}
}
export async function getEmailBySessionId(sessionId: string) {
try {
return (
await prisma.session.findUnique({
where: { id: sessionId },
include: {
user: {
select: { email: true },
},
},
})
)?.user.email;
} catch (e) {
console.trace(e);
return null;
}
}
export async function findOrCreateUserAndSession({
confirmedEmail,
expirationTime,
}) {
try {
const expirationDate = new Date(expirationTime);
const result = await prisma.user.upsert({
where: { email: confirmedEmail },
create: {
email: confirmedEmail,
sessions: { create: { expirationDate } },
},
update: { sessions: { create: { expirationDate } } },
// TODO should this be descending? Or is `asc` correct?
include: { sessions: { take: 1, orderBy: { createdAt: "asc" } } },
});
return result.sessions[0].id;
} catch (e) {
console.trace(e);
return null;
}
}
/* Encrypt/decrypt code source: https://github.com/kentcdodds/kentcdodds.com/blob/43130b0d9033219920a46bb8a4f009781afa02f1/app/utils/encryption.server.ts */
const algorithm = "aes-256-ctr";

View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "BSD-3",
"dependencies": {
"@prisma/client": "^4.9.0",
"bcryptjs": "^2.4.3"
},
"devDependencies": {
@ -21,6 +22,7 @@
"elm-tailwind-modules": "^0.3.2",
"elm-tooling": "^1.3.0",
"postcss": "^8.4.5",
"prisma": "^4.9.0",
"tailwindcss": "^2.2.19"
}
},
@ -592,6 +594,38 @@
"node": ">= 8"
}
},
"node_modules/@prisma/client": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz",
"integrity": "sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==",
"hasInstallScript": true,
"dependencies": {
"@prisma/engines-version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5"
},
"engines": {
"node": ">=14.17"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/engines": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.9.0.tgz",
"integrity": "sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==",
"devOptional": true,
"hasInstallScript": true
},
"node_modules/@prisma/engines-version": {
"version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz",
"integrity": "sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA=="
},
"node_modules/@sindresorhus/is": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
@ -4906,6 +4940,23 @@
"node": ">= 0.8"
}
},
"node_modules/prisma": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.9.0.tgz",
"integrity": "sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "4.9.0"
},
"bin": {
"prisma": "build/index.js",
"prisma2": "build/index.js"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@ -6611,6 +6662,25 @@
"fastq": "^1.6.0"
}
},
"@prisma/client": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz",
"integrity": "sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==",
"requires": {
"@prisma/engines-version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5"
}
},
"@prisma/engines": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.9.0.tgz",
"integrity": "sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==",
"devOptional": true
},
"@prisma/engines-version": {
"version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz",
"integrity": "sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA=="
},
"@sindresorhus/is": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
@ -9746,6 +9816,15 @@
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
"dev": true
},
"prisma": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.9.0.tgz",
"integrity": "sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==",
"devOptional": true,
"requires": {
"@prisma/engines": "4.9.0"
}
},
"prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",

View File

@ -21,9 +21,11 @@
"elm-tailwind-modules": "^0.3.2",
"elm-tooling": "^1.3.0",
"postcss": "^8.4.5",
"prisma": "^4.9.0",
"tailwindcss": "^2.2.19"
},
"dependencies": {
"@prisma/client": "^4.9.0",
"bcryptjs": "^2.4.3"
}
}
}

View File

@ -0,0 +1,46 @@
// datasource db {
// provider = "sqlite"
// url = env("DATABASE_URL")
// }
// generator client {
// provider = "prisma-client-js"
// }
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-1.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique(map: "User.email_unique")
sessions Session[]
todos Todo[]
}
model Session {
id String @id @default(uuid())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
expirationDate DateTime
}
model Todo {
id String @id @default(cuid())
title String
complete Boolean
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
}