mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-23 19:29:17 +03:00
Upgraded to Prisma 2.21 (stable migrations) + postgre can be used in local dev.
- `wasp db migrate-save` and `wasp db migrate-up` got replaced with `wasp db migrate-dev`. - Wasp now has a declarative way to express which db is used, postgresql or sqlite: `db { system: PostgreSQL }`. - Prisma is now at the latest version, 2.21. PSL parser was upgraded to work with this. - PostgreSQL can now be used for local development. - We migrated examples/realworld to work with this new version of Wasp.
This commit is contained in:
parent
57cd5a8a6f
commit
f9e8f88b66
1
examples/realworld/.gitignore
vendored
1
examples/realworld/.gitignore
vendored
@ -1 +1,2 @@
|
||||
/.wasp/
|
||||
.env
|
||||
|
@ -5,7 +5,19 @@ Realworld app
|
||||
|
||||
Here, we implement it in Wasp, by following their [specification](https://github.com/gothinkster/realworld/tree/master/spec).
|
||||
|
||||
Todo:
|
||||
# Development
|
||||
|
||||
### Database
|
||||
Wasp needs postgre database running - provide it with database connection URL via env var `DATABASE_URL` - best to do it via .env file.
|
||||
|
||||
Easy way to get going with postgresql database: run db with `docker run --rm --publish 5432:5432 -v postgresql-data:/var/lib/postgresql/data --env POSTGRES_PASSWORD=devpass postgres`.
|
||||
`DATABASE_URL` in this case is `postgresql://postgres:devpass@localhost:5432/postgres`.
|
||||
|
||||
### Running
|
||||
`wasp start`
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] User + auth (JWT).
|
||||
- [x] Login and signup pages.
|
||||
- [x] Settings page with logout button (no user deletion needed).
|
||||
@ -22,7 +34,7 @@ Todo:
|
||||
- [x] Make tags work again (Prisma problems!).
|
||||
- [x] Following other users.
|
||||
- [x] Paginated lists of articles (on profile page, on home page).
|
||||
- [ ] Implement design (use Bootstrap 4 styling?).
|
||||
- [x] Implement design (use Bootstrap 4 styling?).
|
||||
- [ ] Display proper error messages on login/signup.
|
||||
- [ ] Improve error handling in React, we don't do a really good job there.
|
||||
|
||||
|
@ -144,7 +144,20 @@ const ArticleEditor = (props) => {
|
||||
onChange={e => setMarkdownContent(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* TODO(matija): For some reason Prisma dies if we two articles share tag, so we disabled adding tags for now. */}
|
||||
<TextField
|
||||
className={classes.textField}
|
||||
label="Enter tags"
|
||||
fullWidth
|
||||
value={newTagName}
|
||||
onChange={e => setNewTagName(e.target.value)}
|
||||
onKeyPress={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
setTags([...tags, { name: newTagName }])
|
||||
setNewTagName('')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={classes.tags}>
|
||||
{ tags.map(tag => (
|
||||
|
@ -12,6 +12,10 @@ auth {
|
||||
onAuthFailedRedirectTo: "/login"
|
||||
}
|
||||
|
||||
db {
|
||||
system: PostgreSQL
|
||||
}
|
||||
|
||||
// ----------------- Pages ------------------ //
|
||||
|
||||
route "/" -> page Main
|
||||
|
@ -1,145 +0,0 @@
|
||||
# Migration `20201127131647-init`
|
||||
|
||||
This migration has been generated by Martin Sosic at 11/27/2020, 2:16:47 PM.
|
||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||
|
||||
## Database Steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"bio" TEXT,
|
||||
"profilePictureUrl" TEXT
|
||||
)
|
||||
|
||||
CREATE TABLE "Article" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"slug" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"markdownContent" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE TABLE "Comment" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"content" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"articleId" INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE TABLE "ArticleTag" (
|
||||
"name" TEXT NOT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
)
|
||||
|
||||
CREATE TABLE "_FavoritedArticles" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE TABLE "_ArticleToArticleTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "ArticleTag"("name") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE UNIQUE INDEX "User.username_unique" ON "User"("username")
|
||||
|
||||
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email")
|
||||
|
||||
CREATE UNIQUE INDEX "Article.slug_unique" ON "Article"("slug")
|
||||
|
||||
CREATE UNIQUE INDEX "_FavoritedArticles_AB_unique" ON "_FavoritedArticles"("A", "B")
|
||||
|
||||
CREATE INDEX "_FavoritedArticles_B_index" ON "_FavoritedArticles"("B")
|
||||
|
||||
CREATE UNIQUE INDEX "_ArticleToArticleTag_AB_unique" ON "_ArticleToArticleTag"("A", "B")
|
||||
|
||||
CREATE INDEX "_ArticleToArticleTag_B_index" ON "_ArticleToArticleTag"("B")
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
```diff
|
||||
diff --git schema.prisma schema.prisma
|
||||
migration ..20201127131647-init
|
||||
--- datamodel.dml
|
||||
+++ datamodel.dml
|
||||
@@ -1,0 +1,57 @@
|
||||
+
|
||||
+datasource db {
|
||||
+ provider = "sqlite"
|
||||
+ url = "***"
|
||||
+}
|
||||
+
|
||||
+generator client {
|
||||
+ provider = "prisma-client-js"
|
||||
+ output = "../server/node_modules/.prisma/client"
|
||||
+}
|
||||
+
|
||||
+model User {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ username String @unique
|
||||
+ email String @unique
|
||||
+ password String
|
||||
+ bio String?
|
||||
+ profilePictureUrl String?
|
||||
+
|
||||
+ articles Article[]
|
||||
+ comments Comment[]
|
||||
+ favoriteArticles Article[] @relation("FavoritedArticles")
|
||||
+}
|
||||
+
|
||||
+model Article {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ slug String @unique
|
||||
+ createdAt DateTime @default(now())
|
||||
+ updatedAt DateTime @updatedAt
|
||||
+ title String
|
||||
+ description String
|
||||
+ markdownContent String
|
||||
+
|
||||
+ user User @relation(fields: [userId], references: [id])
|
||||
+ userId Int
|
||||
+ comments Comment[]
|
||||
+ tags ArticleTag[]
|
||||
+ favoritedBy User[] @relation("FavoritedArticles")
|
||||
+}
|
||||
+
|
||||
+model Comment {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ createdAt DateTime @default(now())
|
||||
+ content String
|
||||
+
|
||||
+ user User @relation(fields: [userId], references: [id])
|
||||
+ userId Int
|
||||
+ article Article @relation(fields: [articleId], references: [id])
|
||||
+ articleId Int
|
||||
+}
|
||||
+
|
||||
+model ArticleTag {
|
||||
+ name String @id
|
||||
+
|
||||
+ articles Article[]
|
||||
+}
|
||||
+
|
||||
```
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "***"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../server/node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
bio String?
|
||||
profilePictureUrl String?
|
||||
|
||||
articles Article[]
|
||||
comments Comment[]
|
||||
favoriteArticles Article[] @relation("FavoritedArticles")
|
||||
}
|
||||
|
||||
model Article {
|
||||
id Int @id @default(autoincrement())
|
||||
slug String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
title String
|
||||
description String
|
||||
markdownContent String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
comments Comment[]
|
||||
tags ArticleTag[]
|
||||
favoritedBy User[] @relation("FavoritedArticles")
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
content String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
article Article @relation(fields: [articleId], references: [id])
|
||||
articleId Int
|
||||
}
|
||||
|
||||
model ArticleTag {
|
||||
name String @id
|
||||
|
||||
articles Article[]
|
||||
}
|
||||
|
@ -1,633 +0,0 @@
|
||||
{
|
||||
"version": "0.3.14-fixed",
|
||||
"steps": [
|
||||
{
|
||||
"tag": "CreateSource",
|
||||
"source": "db"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "provider",
|
||||
"value": "\"sqlite\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "url",
|
||||
"value": "\"***\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "User"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "username",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "username"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "email",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "email"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "password",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "bio",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "profilePictureUrl",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "articles",
|
||||
"type": "Article",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "comments",
|
||||
"type": "Comment",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "favoriteArticles",
|
||||
"type": "Article",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "favoriteArticles"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "favoriteArticles"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "\"FavoritedArticles\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "Article"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "slug",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "slug"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "createdAt",
|
||||
"type": "DateTime",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "createdAt"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "createdAt"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "now()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "updatedAt",
|
||||
"type": "DateTime",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "updatedAt"
|
||||
},
|
||||
"directive": "updatedAt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "title",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "description",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "markdownContent",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "user",
|
||||
"type": "User",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "fields",
|
||||
"value": "[userId]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "userId",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "comments",
|
||||
"type": "Comment",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "tags",
|
||||
"type": "ArticleTag",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Article",
|
||||
"field": "favoritedBy",
|
||||
"type": "User",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "favoritedBy"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Article",
|
||||
"field": "favoritedBy"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "\"FavoritedArticles\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "Comment"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "createdAt",
|
||||
"type": "DateTime",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "createdAt"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "createdAt"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "now()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "content",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "user",
|
||||
"type": "User",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "fields",
|
||||
"value": "[userId]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "userId",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "article",
|
||||
"type": "Article",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "article"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "article"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "fields",
|
||||
"value": "[articleId]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Comment",
|
||||
"field": "article"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Comment",
|
||||
"field": "articleId",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "ArticleTag"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "ArticleTag",
|
||||
"field": "name",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "ArticleTag",
|
||||
"field": "name"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "ArticleTag",
|
||||
"field": "articles",
|
||||
"type": "Article",
|
||||
"arity": "List"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
# Migration `20201127145135-added-following-of-other-users`
|
||||
|
||||
This migration has been generated by Martin Sosic at 11/27/2020, 3:51:35 PM.
|
||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||
|
||||
## Database Steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE "_FollowedUser" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY ("A") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE UNIQUE INDEX "_FollowedUser_AB_unique" ON "_FollowedUser"("A", "B")
|
||||
|
||||
CREATE INDEX "_FollowedUser_B_index" ON "_FollowedUser"("B")
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
```diff
|
||||
diff --git schema.prisma schema.prisma
|
||||
migration 20201127131647-init..20201127145135-added-following-of-other-users
|
||||
--- datamodel.dml
|
||||
+++ datamodel.dml
|
||||
@@ -1,8 +1,8 @@
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
- url = "***"
|
||||
+ url = "***"
|
||||
}
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
@@ -19,8 +19,10 @@
|
||||
articles Article[]
|
||||
comments Comment[]
|
||||
favoriteArticles Article[] @relation("FavoritedArticles")
|
||||
+ followedBy User[] @relation("FollowedUser", references: [id])
|
||||
+ following User[] @relation("FollowedUser", references: [id])
|
||||
}
|
||||
model Article {
|
||||
id Int @id @default(autoincrement())
|
||||
```
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "***"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../server/node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
bio String?
|
||||
profilePictureUrl String?
|
||||
|
||||
articles Article[]
|
||||
comments Comment[]
|
||||
favoriteArticles Article[] @relation("FavoritedArticles")
|
||||
followedBy User[] @relation("FollowedUser", references: [id])
|
||||
following User[] @relation("FollowedUser", references: [id])
|
||||
}
|
||||
|
||||
model Article {
|
||||
id Int @id @default(autoincrement())
|
||||
slug String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
title String
|
||||
description String
|
||||
markdownContent String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
comments Comment[]
|
||||
tags ArticleTag[]
|
||||
favoritedBy User[] @relation("FavoritedArticles")
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
content String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
article Article @relation(fields: [articleId], references: [id])
|
||||
articleId Int
|
||||
}
|
||||
|
||||
model ArticleTag {
|
||||
name String @id
|
||||
|
||||
articles Article[]
|
||||
}
|
||||
|
@ -1,97 +0,0 @@
|
||||
{
|
||||
"version": "0.3.14-fixed",
|
||||
"steps": [
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "followedBy",
|
||||
"type": "User",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "followedBy"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "followedBy"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "\"FollowedUser\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "followedBy"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "following",
|
||||
"type": "User",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "following"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "following"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "\"FollowedUser\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "following"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
}
|
||||
]
|
||||
}
|
115
examples/realworld/migrations/20210419141442_/migration.sql
Normal file
115
examples/realworld/migrations/20210419141442_/migration.sql
Normal file
@ -0,0 +1,115 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"bio" TEXT,
|
||||
"profilePictureUrl" TEXT,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Article" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"markdownContent" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Comment" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"content" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"articleId" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ArticleTag" (
|
||||
"name" TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY ("name")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_FavoritedArticles" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_FollowedUser" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ArticleToArticleTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User.username_unique" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Article.slug_unique" ON "Article"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_FavoritedArticles_AB_unique" ON "_FavoritedArticles"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_FavoritedArticles_B_index" ON "_FavoritedArticles"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_FollowedUser_AB_unique" ON "_FollowedUser"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_FollowedUser_B_index" ON "_FollowedUser"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_ArticleToArticleTag_AB_unique" ON "_ArticleToArticleTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ArticleToArticleTag_B_index" ON "_ArticleToArticleTag"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Article" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FavoritedArticles" ADD FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FavoritedArticles" ADD FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FollowedUser" ADD FOREIGN KEY ("A") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FollowedUser" ADD FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ArticleToArticleTag" ADD FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ArticleToArticleTag" ADD FOREIGN KEY ("B") REFERENCES "ArticleTag"("name") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,4 +0,0 @@
|
||||
# Prisma Migrate lockfile v1
|
||||
|
||||
20201127131647-init
|
||||
20201127145135-added-following-of-other-users
|
3
examples/realworld/migrations/migration_lock.toml
Normal file
3
examples/realworld/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
@ -1,33 +1,34 @@
|
||||
module Command.Db.Migrate
|
||||
( migrateSave
|
||||
, migrateUp
|
||||
( migrateDev
|
||||
, copyDbMigrationsDir
|
||||
, MigrationDirCopyDirection(..)
|
||||
) where
|
||||
|
||||
import Control.Monad.Catch (catch)
|
||||
import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Path as P
|
||||
import qualified Path.IO as PathIO
|
||||
import Control.Monad.Catch (catch)
|
||||
import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Path as P
|
||||
import qualified Path.IO as PathIO
|
||||
|
||||
import StrongPath ((</>), Abs, Dir, Path)
|
||||
import qualified StrongPath as SP
|
||||
import Command (Command, CommandError(..))
|
||||
import Command.Common (findWaspProjectRootDirFromCwd, waspSaysC)
|
||||
import Command (Command, CommandError (..))
|
||||
import Command.Common (findWaspProjectRootDirFromCwd,
|
||||
waspSaysC)
|
||||
import Common (WaspProjectDir)
|
||||
import qualified Cli.Common
|
||||
import Common (WaspProjectDir)
|
||||
import StrongPath (Abs, Dir, Path, (</>))
|
||||
import qualified StrongPath as SP
|
||||
|
||||
-- Wasp generator interface.
|
||||
import Generator.Common (ProjectRootDir)
|
||||
import Generator.DbGenerator (dbRootDirInProjectRootDir)
|
||||
import qualified Generator.DbGenerator.Operations as DbOps
|
||||
import Generator.DbGenerator (dbRootDirInProjectRootDir)
|
||||
import Generator.Common (ProjectRootDir)
|
||||
|
||||
|
||||
migrateSave :: String -> Command ()
|
||||
migrateSave migrationName = do
|
||||
migrateDev :: Command ()
|
||||
migrateDev = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let genProjectRootDir = waspProjectDir </> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
let genProjectRootDir = waspProjectDir
|
||||
</> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
</> Cli.Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
-- TODO(matija): It might make sense that this (copying migrations folder from source to
|
||||
@ -35,66 +36,31 @@ migrateSave migrationName = do
|
||||
-- considered part of a "source" code, then generator could take care of it and this command
|
||||
-- wouldn't have to deal with it. We opened an issue on Github about this.
|
||||
--
|
||||
-- NOTE(matija): we need to copy migrations down before running "migrate save" to make sure
|
||||
-- NOTE(matija): we need to copy migrations down before running "migrate dev" to make sure
|
||||
-- all the latest migrations are in the generated project (e.g. Wasp dev checked out something
|
||||
-- new) - otherwise "save" would create a new migration for that and we would end up with two
|
||||
-- new) - otherwise "dev" would create a new migration for that and we would end up with two
|
||||
-- migrations doing the same thing (which might result in conflict, e.g. during db creation).
|
||||
waspSaysC "Copying migrations folder from Wasp to Prisma project..."
|
||||
copyDbMigDirDownResult <- liftIO $ copyDbMigrationsDir CopyMigDirDown waspProjectDir
|
||||
genProjectRootDir
|
||||
case copyDbMigDirDownResult of
|
||||
Nothing -> waspSaysC "Done."
|
||||
Just err -> throwError $ CommandError $ "Copying migration folder failed: " ++ err
|
||||
copyDbMigrationDir waspProjectDir genProjectRootDir CopyMigDirDown
|
||||
|
||||
waspSaysC "Checking for changes in schema to save..."
|
||||
migrateSaveResult <- liftIO $ DbOps.migrateSave genProjectRootDir migrationName
|
||||
case migrateSaveResult of
|
||||
Left migrateSaveError -> throwError $ CommandError $ "Migrate save failed: "
|
||||
++ migrateSaveError
|
||||
Right () -> waspSaysC "Done."
|
||||
waspSaysC "Performing migration..."
|
||||
migrateResult <- liftIO $ DbOps.migrateDev genProjectRootDir
|
||||
case migrateResult of
|
||||
Left migrateError ->
|
||||
throwError $ CommandError $ "Migrate dev failed: " <> migrateError
|
||||
Right () -> waspSaysC "Migration done."
|
||||
|
||||
waspSaysC "Copying migrations folder from Prisma to Wasp project..."
|
||||
copyDbMigDirUpResult <- liftIO $ copyDbMigrationsDir CopyMigDirUp waspProjectDir
|
||||
genProjectRootDir
|
||||
case copyDbMigDirUpResult of
|
||||
Nothing -> waspSaysC "Done."
|
||||
Just err -> throwError $ CommandError $ "Copying migration folder failed: " ++ err
|
||||
|
||||
applyAvailableMigrationsAndGenerateClient genProjectRootDir
|
||||
copyDbMigrationDir waspProjectDir genProjectRootDir CopyMigDirUp
|
||||
|
||||
waspSaysC "All done!"
|
||||
|
||||
migrateUp :: Command ()
|
||||
migrateUp = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let genProjectRootDir = waspProjectDir </> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
</> Cli.Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
waspSaysC "Copying migrations folder from Wasp to Prisma project..."
|
||||
copyDbMigDirResult <- liftIO $ copyDbMigrationsDir CopyMigDirDown waspProjectDir
|
||||
genProjectRootDir
|
||||
case copyDbMigDirResult of
|
||||
Nothing -> waspSaysC "Done."
|
||||
Just err -> throwError $ CommandError $ "Copying migration folder failed: " ++ err
|
||||
|
||||
applyAvailableMigrationsAndGenerateClient genProjectRootDir
|
||||
|
||||
waspSaysC "All done!"
|
||||
|
||||
applyAvailableMigrationsAndGenerateClient :: Path Abs (Dir ProjectRootDir) -> Command ()
|
||||
applyAvailableMigrationsAndGenerateClient genProjectRootDir = do
|
||||
waspSaysC "Checking for migrations to apply..."
|
||||
migrateUpResult <- liftIO $ DbOps.migrateUp genProjectRootDir
|
||||
case migrateUpResult of
|
||||
Left migrateUpError -> throwError $ CommandError $ "Migrate up failed: " ++ migrateUpError
|
||||
Right () -> waspSaysC "Done."
|
||||
|
||||
waspSaysC "Generating Prisma client..."
|
||||
genClientResult <- liftIO $ DbOps.generateClient genProjectRootDir
|
||||
case genClientResult of
|
||||
Left genClientError -> throwError $ CommandError $ "Generating client failed: " ++
|
||||
genClientError
|
||||
Right () -> waspSaysC "Done."
|
||||
where
|
||||
copyDbMigrationDir waspProjectDir genProjectRootDir copyDirection = do
|
||||
copyDbMigDirResult <-
|
||||
liftIO $ copyDbMigrationsDir copyDirection waspProjectDir genProjectRootDir
|
||||
case copyDbMigDirResult of
|
||||
Nothing -> waspSaysC "Done copying migrations folder."
|
||||
Just err -> throwError $ CommandError $ "Copying migration folder failed: " ++ err
|
||||
|
||||
|
||||
data MigrationDirCopyDirection = CopyMigDirUp | CopyMigDirDown deriving (Eq)
|
||||
|
@ -3,10 +3,10 @@ module Main where
|
||||
import Control.Concurrent (threadDelay)
|
||||
import qualified Control.Concurrent.Async as Async
|
||||
import Control.Monad (void)
|
||||
import Data.Char (isSpace)
|
||||
import Data.Version (showVersion)
|
||||
import Paths_waspc (version)
|
||||
import System.Environment
|
||||
import Data.Char (isSpace)
|
||||
|
||||
import Command (runCommand)
|
||||
import Command.Build (build)
|
||||
@ -15,7 +15,7 @@ import Command.Clean (clean)
|
||||
import Command.Compile (compile)
|
||||
import Command.CreateNewProject (createNewProject)
|
||||
import Command.Db (runDbCommand, studio)
|
||||
import Command.Db.Migrate (migrateSave, migrateUp)
|
||||
import qualified Command.Db.Migrate
|
||||
import Command.Start (start)
|
||||
import qualified Command.Telemetry as Telemetry
|
||||
import qualified Util.Terminal as Term
|
||||
@ -74,7 +74,7 @@ printUsage = putStrLn $ unlines
|
||||
, title "EXAMPLES"
|
||||
, " wasp new MyApp"
|
||||
, " wasp start"
|
||||
, " wasp db migrate-save \"init\""
|
||||
, " wasp db migrate-dev"
|
||||
, ""
|
||||
, Term.applyStyles [Term.Green] "Docs:" ++ " https://wasp-lang.dev/docs"
|
||||
, Term.applyStyles [Term.Magenta] "Discord (chat):" ++ " https://discord.gg/rzdnErX"
|
||||
@ -86,10 +86,9 @@ printVersion = putStrLn $ showVersion version
|
||||
-- TODO(matija): maybe extract to a separate module, e.g. DbCli.hs?
|
||||
dbCli :: [String] -> IO ()
|
||||
dbCli args = case args of
|
||||
["migrate-save", migrationName] -> runDbCommand $ migrateSave migrationName
|
||||
["migrate-up"] -> runDbCommand migrateUp
|
||||
["studio"] -> runDbCommand studio
|
||||
_ -> printDbUsage
|
||||
["migrate-dev"] -> runDbCommand Command.Db.Migrate.migrateDev
|
||||
["studio"] -> runDbCommand studio
|
||||
_ -> printDbUsage
|
||||
|
||||
printDbUsage :: IO ()
|
||||
printDbUsage = putStrLn $ unlines
|
||||
@ -97,13 +96,16 @@ printDbUsage = putStrLn $ unlines
|
||||
, " wasp db <command> [command-args]"
|
||||
, ""
|
||||
, title "COMMANDS"
|
||||
, cmd " migrate-save <migration-name> Saves a migration for updating to current schema."
|
||||
, cmd " migrate-up Applies all migrations, updating your db to the current schema."
|
||||
, cmd " studio GUI for inspecting your database."
|
||||
, cmd (
|
||||
" migrate-dev Ensures dev database corresponds to the current state of schema(entities):\n" <>
|
||||
" - Generates a new migration if there are changes in the schema.\n" <>
|
||||
" - Applies any pending migrations to the database."
|
||||
)
|
||||
, cmd " studio GUI for inspecting your database."
|
||||
, ""
|
||||
, title "EXAMPLES"
|
||||
, " wasp db migrate-save \"Added description field.\""
|
||||
, " wasp db migrate-up"
|
||||
, " wasp db migrate-dev"
|
||||
, " wasp db studio"
|
||||
]
|
||||
|
||||
title :: String -> String
|
||||
|
@ -7,8 +7,8 @@
|
||||
"scripts": {
|
||||
"start": "nodemon -r dotenv/config ./src/server.js",
|
||||
"debug": "DEBUG=server:* npm start",
|
||||
"db-migrate-save": "prisma --experimental migrate save --schema=../db/schema.prisma",
|
||||
"db-migrate": "prisma --experimental migrate up --schema=../db/schema.prisma",
|
||||
"db-migrate-prod": "prisma migrate deploy --schema=../db/schema.prisma",
|
||||
"db-migrate-dev": "prisma migrate dev --schema=../db/schema.prisma",
|
||||
"start-production": "{=& startProductionScript =}",
|
||||
"standard": "standard"
|
||||
},
|
||||
@ -22,6 +22,6 @@
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.4",
|
||||
"standard": "^14.3.4",
|
||||
"@prisma/cli": "2.12.1"
|
||||
"prisma": "2.21.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
# Migration `20201028133236-init`
|
||||
|
||||
This migration has been generated by Matija Sosic at 10/28/2020, 2:32:36 PM.
|
||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||
|
||||
## Database Steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL
|
||||
)
|
||||
|
||||
CREATE TABLE "Task" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"description" TEXT NOT NULL,
|
||||
"isDone" BOOLEAN NOT NULL DEFAULT false,
|
||||
"userId" INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
||||
|
||||
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email")
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
```diff
|
||||
diff --git schema.prisma schema.prisma
|
||||
migration ..20201028133236-init
|
||||
--- datamodel.dml
|
||||
+++ datamodel.dml
|
||||
@@ -1,0 +1,26 @@
|
||||
+
|
||||
+datasource db {
|
||||
+ provider = "sqlite"
|
||||
+ url = "***"
|
||||
+}
|
||||
+
|
||||
+generator client {
|
||||
+ provider = "prisma-client-js"
|
||||
+ output = "../server/node_modules/.prisma/client"
|
||||
+}
|
||||
+
|
||||
+model User {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ email String @unique
|
||||
+ password String
|
||||
+ tasks Task[]
|
||||
+}
|
||||
+
|
||||
+model Task {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ description String
|
||||
+ isDone Boolean @default(false)
|
||||
+ user User @relation(fields: [userId], references: [id])
|
||||
+ userId Int
|
||||
+}
|
||||
+
|
||||
```
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "***"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../server/node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
email String @unique
|
||||
password String
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Task {
|
||||
id Int @id @default(autoincrement())
|
||||
description String
|
||||
isDone Boolean @default(false)
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
}
|
||||
|
@ -1,245 +0,0 @@
|
||||
{
|
||||
"version": "0.3.14-fixed",
|
||||
"steps": [
|
||||
{
|
||||
"tag": "CreateSource",
|
||||
"source": "db"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "provider",
|
||||
"value": "\"sqlite\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "url",
|
||||
"value": "\"***\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "User"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "email",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "email"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "password",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "tasks",
|
||||
"type": "Task",
|
||||
"arity": "List"
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "Task"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "description",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "isDone",
|
||||
"type": "Boolean",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "isDone"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "isDone"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "user",
|
||||
"type": "User",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "fields",
|
||||
"value": "[userId]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "user"
|
||||
},
|
||||
"directive": "relation"
|
||||
},
|
||||
"argument": "references",
|
||||
"value": "[id]"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "userId",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# Prisma Migrate lockfile v1
|
||||
|
||||
20201028133236-init
|
@ -6,6 +6,7 @@ module Generator.DbGenerator
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import qualified Path as P
|
||||
import Data.Maybe (fromMaybe)
|
||||
|
||||
import CompileOptions (CompileOptions)
|
||||
import Generator.Common (ProjectRootDir)
|
||||
@ -17,6 +18,7 @@ import StrongPath (Dir, File, Path, Rel, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import Wasp (Wasp)
|
||||
import qualified Wasp
|
||||
import qualified Wasp.Db
|
||||
import Wasp.Entity (Entity)
|
||||
import qualified Wasp.Entity
|
||||
|
||||
@ -61,9 +63,13 @@ genPrismaSchema wasp = createTemplateFileDraft dstPath tmplSrcPath (Just templat
|
||||
, "datasourceUrl" .= (datasourceUrl :: String)
|
||||
]
|
||||
|
||||
isBuild = Wasp.getIsBuild wasp
|
||||
(datasourceProvider, datasourceUrl) = if isBuild then ("postgresql", "env(\"DATABASE_URL\")")
|
||||
else ("sqlite", "\"file:./dev.db\"")
|
||||
dbSystem = fromMaybe Wasp.Db.SQLite $ Wasp.Db._system <$> Wasp.getDb wasp
|
||||
(datasourceProvider, datasourceUrl) = case dbSystem of
|
||||
Wasp.Db.PostgreSQL -> ("postgresql", "env(\"DATABASE_URL\")")
|
||||
-- TODO: Report this error with some better mechanism, not `error`.
|
||||
Wasp.Db.SQLite -> if Wasp.getIsBuild wasp
|
||||
then error "SQLite is not supported in production. Set db.system to smth else."
|
||||
else ("sqlite", "\"file:./dev.db\"")
|
||||
|
||||
entityToPslModelSchema :: Entity -> String
|
||||
entityToPslModelSchema entity = Psl.Generator.Model.generateModel $
|
||||
|
@ -1,7 +1,5 @@
|
||||
module Generator.DbGenerator.Jobs
|
||||
( migrateSave
|
||||
, migrateUp
|
||||
, generateClient
|
||||
( migrateDev
|
||||
, runStudio
|
||||
) where
|
||||
|
||||
@ -13,43 +11,16 @@ import qualified StrongPath as SP
|
||||
import Generator.ServerGenerator.Common (serverRootDirInProjectRootDir)
|
||||
import Generator.DbGenerator (dbSchemaFileInProjectRootDir)
|
||||
|
||||
-- | Runs `prisma migrate save` - creates migration folder for the latest schema changes.
|
||||
migrateSave :: Path Abs (Dir ProjectRootDir) -> String -> J.Job
|
||||
migrateSave projectDir migrationName = do
|
||||
|
||||
migrateDev :: Path Abs (Dir ProjectRootDir) -> J.Job
|
||||
migrateDev projectDir = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
-- NOTE(matija): We are running this command from server's root dir since that is where
|
||||
-- Prisma packages (cli and client) are currently installed.
|
||||
runNodeCommandAsJob serverDir "npx"
|
||||
[ "prisma", "migrate", "save"
|
||||
, "--schema", SP.toFilePath schemaFile
|
||||
, "--name", migrationName
|
||||
, "--create-db" -- Creates db if it doesn't already exist. Otherwise would stop and ask.
|
||||
, "--experimental"
|
||||
] J.Db
|
||||
|
||||
-- | Runs `prisma migrate up` - applies all the available migrations.
|
||||
migrateUp :: Path Abs (Dir ProjectRootDir) -> J.Job
|
||||
migrateUp projectDir = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
runNodeCommandAsJob serverDir "npx"
|
||||
[ "prisma", "migrate", "up"
|
||||
, "--schema", SP.toFilePath schemaFile
|
||||
, "--create-db" -- Creates db if it doesn't already exist. Otherwise would stop and ask.
|
||||
, "--experimental"
|
||||
] J.Db
|
||||
|
||||
-- | Runs `prisma generate` - (re)generates db client api.
|
||||
generateClient :: Path Abs (Dir ProjectRootDir) -> J.Job
|
||||
generateClient projectDir = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
runNodeCommandAsJob serverDir "npx"
|
||||
[ "prisma", "generate"
|
||||
[ "prisma", "migrate", "dev"
|
||||
, "--schema", SP.toFilePath schemaFile
|
||||
] J.Db
|
||||
|
||||
@ -62,6 +33,4 @@ runStudio projectDir = do
|
||||
runNodeCommandAsJob serverDir "npx"
|
||||
[ "prisma", "studio"
|
||||
, "--schema", SP.toFilePath schemaFile
|
||||
, "--experimental"
|
||||
] J.Db
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
module Generator.DbGenerator.Operations
|
||||
( migrateSave
|
||||
, migrateUp
|
||||
, generateClient
|
||||
( migrateDev
|
||||
) where
|
||||
|
||||
import Control.Concurrent (Chan, newChan, readChan)
|
||||
@ -22,32 +20,11 @@ printJobMsgsUntilExitReceived chan = do
|
||||
J.JobOutput {} -> printJobMessage jobMsg >> printJobMsgsUntilExitReceived chan
|
||||
J.JobExit {} -> return ()
|
||||
|
||||
-- | Checks for the changes in db schema file and creates and saves db migration info, but it
|
||||
-- does not execute it.
|
||||
migrateSave :: Path Abs (Dir ProjectRootDir) -> String -> IO (Either String ())
|
||||
migrateSave projectDir migrationName = do
|
||||
migrateDev :: Path Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
migrateDev projectDir = do
|
||||
chan <- newChan
|
||||
(_, dbExitCode) <- concurrently (printJobMsgsUntilExitReceived chan)
|
||||
(DbJobs.migrateSave projectDir migrationName chan)
|
||||
(DbJobs.migrateDev projectDir chan)
|
||||
case dbExitCode of
|
||||
ExitSuccess -> return (Right ())
|
||||
ExitFailure code -> return $ Left $ "Migrate save failed with exit code: " ++ show code
|
||||
|
||||
migrateUp :: Path Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
migrateUp projectDir = do
|
||||
chan <- newChan
|
||||
(_, dbExitCode) <- concurrently (printJobMsgsUntilExitReceived chan)
|
||||
(DbJobs.migrateUp projectDir chan)
|
||||
case dbExitCode of
|
||||
ExitSuccess -> return (Right ())
|
||||
ExitFailure code -> return $ Left $ "Migrate up failed with exit code: " ++ show code
|
||||
|
||||
generateClient :: Path Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
generateClient projectDir = do
|
||||
chan <- newChan
|
||||
(_, dbExitCode) <- concurrently (printJobMsgsUntilExitReceived chan)
|
||||
(DbJobs.generateClient projectDir chan)
|
||||
case dbExitCode of
|
||||
ExitSuccess -> return (Right ())
|
||||
ExitFailure code -> return $ Left $ "Client generation failed with exit code: " ++ show code
|
||||
|
||||
ExitFailure code -> return $ Left $ "Migrate (dev) failed with exit code: " ++ show code
|
||||
|
@ -88,7 +88,7 @@ genPackageJson wasp waspDeps = C.makeTemplateFD
|
||||
, "depsChunk" .= toPackageJsonDependenciesString (resolvedWaspDeps ++ resolvedUserDeps)
|
||||
, "nodeVersion" .= nodeVersionAsText
|
||||
, "startProductionScript" .= concat
|
||||
[ if not (null $ Wasp.getPSLEntities wasp) then "npm run db-migrate && " else ""
|
||||
[ if not (null $ Wasp.getPSLEntities wasp) then "npm run db-migrate-prod && " else ""
|
||||
, "NODE_ENV=production node ./src/server.js"
|
||||
]
|
||||
])
|
||||
@ -108,7 +108,7 @@ waspNpmDeps = ND.fromList
|
||||
, ("debug", "~2.6.9")
|
||||
, ("express", "~4.16.1")
|
||||
, ("morgan", "~1.9.1")
|
||||
, ("@prisma/client", "2.12.1")
|
||||
, ("@prisma/client", "2.21.0")
|
||||
, ("jsonwebtoken", "^8.5.1")
|
||||
, ("secure-password", "^4.0.0")
|
||||
, ("dotenv", "8.2.0")
|
||||
|
@ -28,6 +28,9 @@ reservedNameEntity = "entity"
|
||||
reservedNameAuth :: String
|
||||
reservedNameAuth = "auth"
|
||||
|
||||
reservedNameDb :: String
|
||||
reservedNameDb = "db"
|
||||
|
||||
reservedNameQuery :: String
|
||||
reservedNameQuery = "query"
|
||||
|
||||
|
@ -11,6 +11,7 @@ import Lexer
|
||||
|
||||
import Parser.App (app)
|
||||
import Parser.Auth (auth)
|
||||
import Parser.Db (db)
|
||||
import Parser.Route (route)
|
||||
import Parser.Page (page)
|
||||
import Parser.Entity (entity)
|
||||
@ -26,6 +27,7 @@ waspElement
|
||||
= waspElementApp
|
||||
<|> waspElementAuth
|
||||
<|> waspElementPage
|
||||
<|> waspElementDb
|
||||
<|> waspElementRoute
|
||||
<|> waspElementEntity
|
||||
<|> waspElementQuery
|
||||
@ -38,6 +40,9 @@ waspElementApp = Wasp.WaspElementApp <$> app
|
||||
waspElementAuth :: Parser Wasp.WaspElement
|
||||
waspElementAuth = Wasp.WaspElementAuth <$> auth
|
||||
|
||||
waspElementDb :: Parser Wasp.WaspElement
|
||||
waspElementDb = Wasp.WaspElementDb <$> db
|
||||
|
||||
waspElementPage :: Parser Wasp.WaspElement
|
||||
waspElementPage = Wasp.WaspElementPage <$> page
|
||||
|
||||
|
36
waspc/src/Parser/Db.hs
Normal file
36
waspc/src/Parser/Db.hs
Normal file
@ -0,0 +1,36 @@
|
||||
module Parser.Db
|
||||
( db
|
||||
) where
|
||||
|
||||
import Text.Parsec.String (Parser)
|
||||
import Text.Parsec ((<|>), try)
|
||||
import Data.Maybe (listToMaybe, fromMaybe)
|
||||
|
||||
import qualified Wasp.Db
|
||||
import qualified Parser.Common as P
|
||||
import qualified Lexer as L
|
||||
|
||||
db :: Parser Wasp.Db.Db
|
||||
db = do
|
||||
L.reserved L.reservedNameDb
|
||||
dbProperties <- P.waspClosure (L.commaSep1 dbProperty)
|
||||
|
||||
system <- fromMaybe (fail "'system' property is required!") $ return <$>
|
||||
listToMaybe [p | DbPropertySystem p <- dbProperties]
|
||||
|
||||
return Wasp.Db.Db
|
||||
{ Wasp.Db._system = system
|
||||
}
|
||||
|
||||
data DbProperty
|
||||
= DbPropertySystem Wasp.Db.DbSystem
|
||||
|
||||
dbProperty :: Parser DbProperty
|
||||
dbProperty
|
||||
= dbPropertySystem
|
||||
|
||||
dbPropertySystem :: Parser DbProperty
|
||||
dbPropertySystem = DbPropertySystem <$> (P.waspProperty "system" dbPropertySystemValue)
|
||||
where
|
||||
dbPropertySystemValue = try (L.symbol "PostgreSQL" >> return Wasp.Db.PostgreSQL)
|
||||
<|> try (L.symbol "SQLite" >> return Wasp.Db.SQLite)
|
@ -50,10 +50,14 @@ getEntityFields (PslModel.Body pslElements) = map pslFieldToEntityField pslField
|
||||
|
||||
pslFieldTypeToScalar :: PslModel.FieldType -> Entity.Scalar
|
||||
pslFieldTypeToScalar fType = case fType of
|
||||
PslModel.String -> Entity.String
|
||||
PslModel.Boolean -> Entity.Boolean
|
||||
PslModel.Int -> Entity.Int
|
||||
PslModel.Float -> Entity.Float
|
||||
PslModel.DateTime -> Entity.DateTime
|
||||
PslModel.Json -> Entity.Json
|
||||
PslModel.UserType name -> Entity.UserType name
|
||||
PslModel.String -> Entity.String
|
||||
PslModel.Boolean -> Entity.Boolean
|
||||
PslModel.Int -> Entity.Int
|
||||
PslModel.BigInt -> Entity.BigInt
|
||||
PslModel.Float -> Entity.Float
|
||||
PslModel.Decimal -> Entity.Decimal
|
||||
PslModel.DateTime -> Entity.DateTime
|
||||
PslModel.Json -> Entity.Json
|
||||
PslModel.Bytes -> Entity.Bytes
|
||||
PslModel.UserType typeName -> Entity.UserType typeName
|
||||
PslModel.Unsupported typeName -> Entity.Unsupported typeName
|
||||
|
@ -22,12 +22,27 @@ data Field = Field
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
data FieldType = String | Boolean | Int | Float | DateTime | Json | UserType String
|
||||
data FieldType = String
|
||||
| Boolean
|
||||
| Int
|
||||
| BigInt
|
||||
| Float
|
||||
| Decimal
|
||||
| DateTime
|
||||
| Json
|
||||
| Bytes
|
||||
| Unsupported String
|
||||
| UserType String
|
||||
deriving (Show, Eq)
|
||||
|
||||
data FieldTypeModifier = List | Optional
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- NOTE: We don't differentiate "native database type" attributes from normal attributes right now,
|
||||
-- they are all represented with `data Attribute`.
|
||||
-- We just represent them as a normal attribute with attrName being e.g. "db.VarChar".
|
||||
-- TODO: In the future, we might want to be "smarter" about this and actually have a special representation
|
||||
-- for them -> but let's see if that will be needed.
|
||||
data Attribute = Attribute
|
||||
{ _attrName :: String
|
||||
, _attrArgs :: [AttributeArg]
|
||||
|
@ -23,13 +23,17 @@ generateElement (Ast.ElementBlockAttribute attribute) =
|
||||
|
||||
generateFieldType :: Ast.FieldType -> String
|
||||
generateFieldType fieldType = case fieldType of
|
||||
Ast.String -> "String"
|
||||
Ast.Boolean -> "Boolean"
|
||||
Ast.Int -> "Int"
|
||||
Ast.Float -> "Float"
|
||||
Ast.DateTime -> "DateTime"
|
||||
Ast.Json -> "Json"
|
||||
Ast.UserType label -> label
|
||||
Ast.String -> "String"
|
||||
Ast.Boolean -> "Boolean"
|
||||
Ast.Int -> "Int"
|
||||
Ast.BigInt -> "BigInt"
|
||||
Ast.Float -> "Float"
|
||||
Ast.Decimal -> "Decimal"
|
||||
Ast.DateTime -> "DateTime"
|
||||
Ast.Json -> "Json"
|
||||
Ast.Bytes -> "Bytes"
|
||||
Ast.UserType label -> label
|
||||
Ast.Unsupported typeName -> "Unsupported(" ++ show typeName ++ ")"
|
||||
|
||||
generateFieldTypeModifier :: Ast.FieldTypeModifier -> String
|
||||
generateFieldTypeModifier typeModifier = case typeModifier of
|
||||
@ -49,12 +53,12 @@ generateAttributeArg (Ast.AttrArgUnnamed value) = generateAttrArgValue value
|
||||
|
||||
generateAttrArgValue :: Ast.AttrArgValue -> String
|
||||
generateAttrArgValue value = case value of
|
||||
Ast.AttrArgString strValue -> show strValue
|
||||
Ast.AttrArgString strValue -> show strValue
|
||||
Ast.AttrArgIdentifier identifier -> identifier
|
||||
Ast.AttrArgFunc funcName -> funcName ++ "()"
|
||||
Ast.AttrArgFieldRefList refs -> "[" ++ intercalate ", " refs ++ "]"
|
||||
Ast.AttrArgNumber numberStr -> numberStr
|
||||
Ast.AttrArgUnknown unknownStr -> unknownStr
|
||||
Ast.AttrArgFunc funcName -> funcName ++ "()"
|
||||
Ast.AttrArgFieldRefList refs -> "[" ++ intercalate ", " refs ++ "]"
|
||||
Ast.AttrArgNumber numberStr -> numberStr
|
||||
Ast.AttrArgUnknown unknownStr -> unknownStr
|
||||
|
||||
-- TODO: I should make sure to skip attributes that are not known in prisma.
|
||||
-- Or maybe it would be better if that was done in previous step, where
|
||||
|
@ -61,11 +61,15 @@ field = do
|
||||
[ ("String", Model.String)
|
||||
, ("Boolean", Model.Boolean)
|
||||
, ("Int", Model.Int)
|
||||
, ("BigInt", Model.BigInt)
|
||||
, ("Float", Model.Float)
|
||||
, ("Decimal", Model.Decimal)
|
||||
, ("DateTime", Model.DateTime)
|
||||
, ("Json", Model.Json)
|
||||
, ("Bytes", Model.Bytes)
|
||||
]
|
||||
)
|
||||
<|> (try $ Model.Unsupported <$> (T.symbol lexer "Unsupported" >> T.parens lexer (T.stringLiteral lexer)))
|
||||
<|> Model.UserType <$> T.identifier lexer
|
||||
|
||||
-- NOTE: As is Prisma currently implemented, there can be only one type modifier at one time: [] or ?.
|
||||
@ -79,9 +83,22 @@ attribute :: Parser Model.Attribute
|
||||
attribute = do
|
||||
_ <- char '@'
|
||||
name <- T.identifier lexer
|
||||
-- NOTE: we support potential "selector" in order to support native database type attributes.
|
||||
-- These have names with single . in them, like this: @db.VarChar(200), @db.TinyInt(1), ... .
|
||||
-- We are not trying to be very smart here though: we don't check that "db" part matches
|
||||
-- the name of the datasource block name (as it should), and we don't check that "VarChar" part is PascalCase
|
||||
-- (as it should be) or that it is one of the valid values.
|
||||
-- We just treat it as any other attribute, where "db.VarChar" becomes an attribute name.
|
||||
-- In case that we wanted to be smarter, we could expand the AST to have special representation for it.
|
||||
-- Also, we could do some additional checks here in parser (PascalCase), and some additional checks
|
||||
-- in th generator ("db" matching the datasource block name).
|
||||
maybeSelector <- optionMaybe $ try $ char '.' >> T.identifier lexer
|
||||
|
||||
maybeArgs <- optionMaybe (T.parens lexer (T.commaSep1 lexer (try attrArgument)))
|
||||
return $ Model.Attribute
|
||||
{ Model._attrName = name
|
||||
{ Model._attrName = case maybeSelector of
|
||||
Just selector -> name ++ "." ++ selector
|
||||
Nothing -> name
|
||||
, Model._attrArgs = fromMaybe [] maybeArgs
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ module Wasp
|
||||
, getAuth
|
||||
, getPSLEntities
|
||||
|
||||
, getDb
|
||||
|
||||
, module Wasp.Page
|
||||
, getPages
|
||||
, addPage
|
||||
@ -49,6 +51,7 @@ import qualified Util as U
|
||||
import qualified Wasp.Action
|
||||
import Wasp.App
|
||||
import qualified Wasp.Auth
|
||||
import qualified Wasp.Db
|
||||
import Wasp.Entity
|
||||
import Wasp.JsImport
|
||||
import Wasp.NpmDependencies (NpmDependencies)
|
||||
@ -71,6 +74,7 @@ data Wasp = Wasp
|
||||
data WaspElement
|
||||
= WaspElementApp !App
|
||||
| WaspElementAuth !Wasp.Auth.Auth
|
||||
| WaspElementDb !Wasp.Db.Db
|
||||
| WaspElementPage !Page
|
||||
| WaspElementNpmDependencies !NpmDependencies
|
||||
| WaspElementRoute !Route
|
||||
@ -144,12 +148,21 @@ fromApp app = fromWaspElems [WaspElementApp app]
|
||||
-- * Auth
|
||||
|
||||
getAuth :: Wasp -> Maybe Wasp.Auth.Auth
|
||||
getAuth wasp = let auths = [a | WaspElementAuth a <- waspElements wasp] in
|
||||
getAuth wasp = let auths = [a | WaspElementAuth a <- waspElements wasp] in
|
||||
case auths of
|
||||
[] -> Nothing
|
||||
[a] -> Just a
|
||||
_ -> error "Wasp can't contain more than one WaspElementAuth element!"
|
||||
|
||||
-- * Db
|
||||
|
||||
getDb :: Wasp -> Maybe Wasp.Db.Db
|
||||
getDb wasp = let dbs = [db | WaspElementDb db <- waspElements wasp] in
|
||||
case dbs of
|
||||
[] -> Nothing
|
||||
[db] -> Just db
|
||||
_ -> error "Wasp can't contain more than one Db element!"
|
||||
|
||||
-- * NpmDependencies
|
||||
|
||||
getNpmDependencies :: Wasp -> NpmDependencies
|
||||
|
13
waspc/src/Wasp/Db.hs
Normal file
13
waspc/src/Wasp/Db.hs
Normal file
@ -0,0 +1,13 @@
|
||||
module Wasp.Db
|
||||
( Db (..)
|
||||
, DbSystem (..)
|
||||
) where
|
||||
|
||||
data Db = Db
|
||||
{ _system :: !DbSystem
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data DbSystem
|
||||
= PostgreSQL
|
||||
| SQLite
|
||||
deriving (Show, Eq)
|
@ -34,13 +34,17 @@ data Scalar
|
||||
= String
|
||||
| Boolean
|
||||
| Int
|
||||
| BigInt
|
||||
| Float
|
||||
| Decimal
|
||||
| DateTime
|
||||
| Json
|
||||
| Bytes
|
||||
-- | Name of the user-defined type.
|
||||
-- This could be another entity, or maybe an enum,
|
||||
-- we don't know here yet.
|
||||
| UserType String
|
||||
| Unsupported String
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance ToJSON Entity where
|
||||
|
24
waspc/test/Parser/DbTest.hs
Normal file
24
waspc/test/Parser/DbTest.hs
Normal file
@ -0,0 +1,24 @@
|
||||
module Parser.DbTest where
|
||||
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import Data.Either (isLeft)
|
||||
|
||||
import Parser.Common (runWaspParser)
|
||||
import Parser.Db (db)
|
||||
import qualified Wasp.Db
|
||||
|
||||
|
||||
spec_parseDb :: Spec
|
||||
spec_parseDb =
|
||||
describe "Parsing db declaration" $ do
|
||||
let parseDb input = runWaspParser db input
|
||||
|
||||
it "When given a valid db declaration, returns correct AST" $ do
|
||||
parseDb "db { system: PostgreSQL }"
|
||||
`shouldBe` Right (Wasp.Db.Db { Wasp.Db._system = Wasp.Db.PostgreSQL })
|
||||
parseDb "db { system: SQLite }"
|
||||
`shouldBe` Right (Wasp.Db.Db { Wasp.Db._system = Wasp.Db.SQLite })
|
||||
|
||||
it "When given db wasp declaration without 'db', should return Left" $ do
|
||||
isLeft (parseDb "db { }") `shouldBe` True
|
@ -49,7 +49,6 @@ spec_parsePage =
|
||||
, Wasp.Page._component = expectedPageComponentImport
|
||||
, Wasp.Page._authRequired = Just True
|
||||
})
|
||||
|
||||
|
||||
it "When given page wasp declaration without 'page', should return Left" $ do
|
||||
isLeft (parsePage "Landing { component: import Main from \"@ext/pages/Main\" }") `shouldBe` True
|
||||
|
@ -8,8 +8,9 @@ sampleBodySchema :: String
|
||||
sampleBodySchema =
|
||||
unlines
|
||||
[ " id Int @id @default(value: autoincrement())"
|
||||
, " email String?"
|
||||
, " email String? @db.VarChar(200)"
|
||||
, " posts Post[] @relation(\"UserPosts\", references: [id]) @customattr"
|
||||
, " weirdType Unsupported(\"weird\")"
|
||||
, ""
|
||||
, " @@someattr([id, email], 2 + 4, [posts])"
|
||||
]
|
||||
@ -42,7 +43,14 @@ sampleBodyAst =
|
||||
{ AST._name = "email"
|
||||
, AST._type = AST.String
|
||||
, AST._typeModifiers = [AST.Optional]
|
||||
, AST._attrs = []
|
||||
, AST._attrs =
|
||||
[ AST.Attribute
|
||||
{ AST._attrName = "db.VarChar"
|
||||
, AST._attrArgs =
|
||||
[ AST.AttrArgUnnamed (AST.AttrArgNumber "200")
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
, AST.ElementField
|
||||
@ -65,6 +73,14 @@ sampleBodyAst =
|
||||
]
|
||||
}
|
||||
)
|
||||
, AST.ElementField
|
||||
( AST.Field
|
||||
{ AST._name = "weirdType"
|
||||
, AST._type = AST.Unsupported "weird"
|
||||
, AST._typeModifiers = []
|
||||
, AST._attrs = []
|
||||
}
|
||||
)
|
||||
, AST.ElementBlockAttribute
|
||||
( AST.Attribute
|
||||
{ AST._attrName = "someattr"
|
||||
|
@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
|
||||
module Psl.Generator.ModelTest where
|
||||
|
||||
@ -24,9 +25,6 @@ prop_generatePslModel :: Property
|
||||
prop_generatePslModel = mapSize (const 100) $ \modelAst -> within 1000000 $
|
||||
runWaspParser Psl.Parser.Model.model (generateModel modelAst) `shouldBe` Right modelAst
|
||||
|
||||
-- TODO: Figure out what to do with these orphand Arbitrary instances.
|
||||
-- Should they go into src/Psl/Ast/Model.hs?
|
||||
|
||||
instance Arbitrary AST.Model where
|
||||
arbitrary = AST.Model <$> arbitraryIdentifier <*> arbitrary
|
||||
|
||||
@ -59,9 +57,13 @@ instance Arbitrary AST.FieldType where
|
||||
[ return AST.String
|
||||
, return AST.Boolean
|
||||
, return AST.Int
|
||||
, return AST.BigInt
|
||||
, return AST.Float
|
||||
, return AST.Decimal
|
||||
, return AST.DateTime
|
||||
, return AST.Json
|
||||
, return AST.Bytes
|
||||
, AST.Unsupported . show <$> arbitraryIdentifier
|
||||
, AST.UserType <$> arbitraryIdentifier ]
|
||||
|
||||
instance Arbitrary AST.FieldTypeModifier where
|
||||
@ -69,7 +71,10 @@ instance Arbitrary AST.FieldTypeModifier where
|
||||
|
||||
instance Arbitrary AST.Attribute where
|
||||
arbitrary = do
|
||||
name <- arbitraryIdentifier
|
||||
name <- frequency
|
||||
[ (2, arbitraryIdentifier)
|
||||
, (1, ("db." ++) <$> arbitraryIdentifier)
|
||||
]
|
||||
args <- scale (const 5) arbitrary
|
||||
return $ AST.Attribute { AST._attrName = name, AST._attrArgs = args }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user