-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples(crud-simple-todo): init example
- Loading branch information
Showing
10 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../examples/crud-simple-todo/README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
NODE_ENV=development | ||
APP_NAME=exampletodo | ||
|
||
POSTGRES_HOST=127.0.0.1:5432 | ||
POSTGRES_USER=postgres | ||
POSTGRES_PASSWORD=postgres |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Minimal CRUD api for managing todo's | ||
|
||
This project is created using the | ||
[crud-simple-todo](https://github.com/compasjs/compas/tree/main/examples/crud-simple-todo) | ||
template via [create-compas](https://www.npmjs.com/package/create-compas). | ||
|
||
```shell | ||
# Via NPM | ||
npx create-compas@latest --template crud-simple-todo | ||
|
||
# Or with Yarn | ||
yarn create compas --template crud-simple-todo | ||
``` | ||
|
||
It uses a few Compas features, most notably: | ||
|
||
- The code-generators, more specifically, CRUD generators and the generated api | ||
clients | ||
- Postgres related features like migrations and test databases | ||
|
||
## Getting started | ||
|
||
- Start up the development Postgres and Minio instances | ||
- `compas docker up` | ||
- Apply the Postgres migrations | ||
- `compas migrate` | ||
- Regenerate router, validators, types, sql, etc. | ||
- `compas run generate` | ||
- Run the tests | ||
- `compas test --serial` | ||
- Start the API | ||
- `compas run api` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; | ||
|
||
CREATE TABLE migration | ||
( | ||
"namespace" varchar NOT NULL, | ||
"number" int, | ||
"name" varchar NOT NULL, | ||
"createdAt" timestamptz DEFAULT now(), | ||
"hash" varchar | ||
); | ||
|
||
CREATE INDEX migration_namespace_number_idx ON "migration" ("namespace", "number"); | ||
|
||
CREATE TABLE "todo" | ||
( | ||
"id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(), | ||
"title" varchar NOT NULL, | ||
"completedAt" timestamptz NULL, | ||
"createdAt" timestamptz NOT NULL DEFAULT now(), | ||
"updatedAt" timestamptz NOT NULL DEFAULT now() | ||
); | ||
|
||
CREATE INDEX "todoDatesIdx" ON "todo" ("createdAt", "updatedAt"); | ||
CREATE INDEX "todoTitleIdx" ON "todo" ("title"); | ||
CREATE INDEX "todoCompletedAtIdx" ON "todo" ("completedAt"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"private": true, | ||
"version": "0.0.1", | ||
"type": "module", | ||
"dependencies": { | ||
"@compas/cli": "*", | ||
"@compas/server": "*", | ||
"@compas/stdlib": "*", | ||
"@compas/store": "*" | ||
}, | ||
"devDependencies": { | ||
"@compas/code-gen": "*", | ||
"@compas/eslint-plugin": "*" | ||
}, | ||
"prettier": "@compas/eslint-plugin/prettierrc", | ||
"exampleMetadata": { | ||
"generating": "compas run generate", | ||
"testing": [ | ||
"compas migrate", | ||
"compas test --serial" | ||
], | ||
"initMessage": "Started a new project with the 'crud-simple-todo' template. See the README.md for how to get started." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { environment, isProduction, isStaging, mainFn } from "@compas/stdlib"; | ||
import { app, injectServices } from "../src/services.js"; | ||
|
||
mainFn(import.meta, main); | ||
|
||
async function main(logger) { | ||
await injectServices(); | ||
|
||
const port = Number(environment.PORT ?? "3001"); | ||
app.listen(port, () => { | ||
logger.info({ | ||
message: "Listening...", | ||
port, | ||
isProduction: isProduction(), | ||
isStaging: isStaging(), | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { TypeCreator } from "@compas/code-gen"; | ||
import { Generator } from "@compas/code-gen/experimental"; | ||
import { mainFn } from "@compas/stdlib"; | ||
|
||
mainFn(import.meta, main); | ||
|
||
function main() { | ||
const generator = new Generator(); | ||
const T = new TypeCreator("todo"); | ||
|
||
generator.add( | ||
new TypeCreator("database") | ||
.object("todo") | ||
.keys({ | ||
title: T.string().min(3).searchable(), | ||
completedAt: T.date().optional().searchable(), | ||
}) | ||
.enableQueries({ | ||
withDates: true, | ||
}), | ||
|
||
T.crud("/todo").entity(T.reference("database", "todo")).routes({ | ||
listRoute: true, | ||
singleRoute: true, | ||
createRoute: true, | ||
updateRoute: true, | ||
deleteRoute: true, | ||
}), | ||
); | ||
|
||
generator.generate({ | ||
targetLanguage: "js", | ||
outputDirectory: "./src/generated", | ||
generators: { | ||
database: { | ||
target: { | ||
dialect: "postgres", | ||
}, | ||
}, | ||
apiClient: { | ||
target: { | ||
targetRuntime: "node.js", | ||
library: "fetch", | ||
}, | ||
responseValidation: { | ||
looseObjectValidation: false, | ||
}, | ||
}, | ||
router: { | ||
target: { | ||
library: "koa", | ||
}, | ||
}, | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { createBodyParsers, getApp } from "@compas/server"; | ||
import { | ||
createTestPostgresDatabase, | ||
newPostgresConnection, | ||
} from "@compas/store"; | ||
import { router } from "./generated/common/router.js"; | ||
import { todoRegisterCrud } from "./generated/todo/crud.js"; | ||
|
||
export let app = undefined; | ||
|
||
export let sql = undefined; | ||
|
||
export async function injectServices() { | ||
app = getApp({}); | ||
sql = await newPostgresConnection({ max: 10 }); | ||
|
||
await todoRegisterCrud({ | ||
sql, | ||
}); | ||
|
||
app.use(router(createBodyParsers())); | ||
} | ||
|
||
export async function injectTestServices() { | ||
app = getApp({}); | ||
sql = await createTestPostgresDatabase(); | ||
|
||
await todoRegisterCrud({ | ||
sql, | ||
}); | ||
|
||
app.use(router(createBodyParsers())); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { mainTestFn, test } from "@compas/cli"; | ||
import { isNil } from "@compas/stdlib"; | ||
import { cleanupTestPostgresDatabase } from "@compas/store"; | ||
import { | ||
fetchCatchErrorAndWrapWithAppError, | ||
fetchWithBaseUrl, | ||
} from "../src/generated/common/api-client.js"; | ||
import { | ||
apiTodoCreate, | ||
apiTodoList, | ||
apiTodoSingle, | ||
apiTodoUpdate, | ||
} from "../src/generated/todo/apiClient.js"; | ||
import { app, injectTestServices, sql } from "../src/services.js"; | ||
|
||
mainTestFn(import.meta); | ||
|
||
test("crud-simple-todo", async (t) => { | ||
const apiPort = 4441; | ||
|
||
await injectTestServices(); | ||
|
||
const server = app.listen(apiPort); | ||
|
||
const fetchFn = fetchCatchErrorAndWrapWithAppError( | ||
fetchWithBaseUrl(fetch, `http://localhost:${apiPort}/`), | ||
); | ||
|
||
t.test("Empty todo list to start with", async (t) => { | ||
const { total } = await apiTodoList(fetchFn, {}, {}); | ||
|
||
t.equal(total, 0); | ||
}); | ||
|
||
t.test("Create a new todo item", async (t) => { | ||
const { item } = await apiTodoCreate(fetchFn, { | ||
title: "Write more tests", | ||
}); | ||
|
||
t.ok(isNil(item.completedAt), "Todo item should not be completed"); | ||
}); | ||
|
||
t.test("Retrieve all todo items", async (t) => { | ||
const { total, list } = await apiTodoList(fetchFn, {}, {}); | ||
|
||
t.equal(total, 1); | ||
t.equal(list[0].title, "Write more tests"); | ||
}); | ||
|
||
t.test("Retrieve a single todo item", async (t) => { | ||
const { list } = await apiTodoList(fetchFn, {}, {}); | ||
const { item } = await apiTodoSingle(fetchFn, { todoId: list[0].id }); | ||
|
||
t.equal(item.id, list[0].id); | ||
}); | ||
|
||
t.test("Insert and update title of todo", async (t) => { | ||
const { item: insertedItem } = await apiTodoCreate(fetchFn, { | ||
title: "Non-descriptive title", | ||
}); | ||
|
||
await apiTodoUpdate( | ||
fetchFn, | ||
{ | ||
todoId: insertedItem.id, | ||
}, | ||
{ | ||
title: "Descriptive title", | ||
}, | ||
); | ||
|
||
const { item } = await apiTodoSingle(fetchFn, { todoId: insertedItem.id }); | ||
|
||
t.equal(item.title, "Descriptive title"); | ||
t.ok(isNil(item.completedAt), "Todo is not completed yet"); | ||
}); | ||
|
||
t.test("Complete a todo", async (t) => { | ||
const { item: insertedItem } = await apiTodoCreate(fetchFn, { | ||
title: "Non-descriptive title", | ||
}); | ||
|
||
await apiTodoUpdate( | ||
fetchFn, | ||
{ | ||
todoId: insertedItem.id, | ||
}, | ||
{ | ||
title: insertedItem.title, | ||
completedAt: new Date(), | ||
}, | ||
); | ||
|
||
const { item } = await apiTodoSingle(fetchFn, { todoId: insertedItem.id }); | ||
|
||
t.ok(!isNil(item.completedAt)); | ||
}); | ||
|
||
t.test("Search the list on completed todo's", async (t) => { | ||
const { total } = await apiTodoList( | ||
fetchFn, | ||
{}, | ||
{ | ||
where: { | ||
completedAtIsNotNull: true, | ||
}, | ||
}, | ||
); | ||
|
||
t.equal(total, 1); | ||
}); | ||
|
||
t.test("Search the list on todo's that are not completed yet", async (t) => { | ||
const { total } = await apiTodoList( | ||
fetchFn, | ||
{}, | ||
{ | ||
where: { | ||
completedAtIsNull: true, | ||
}, | ||
}, | ||
); | ||
|
||
t.equal(total, 2); | ||
}); | ||
|
||
t.test("Search the list on titles", async (t) => { | ||
const { total } = await apiTodoList( | ||
fetchFn, | ||
{}, | ||
{ | ||
where: { | ||
titleILike: `write%`, | ||
}, | ||
}, | ||
); | ||
|
||
t.equal(total, 1, "Only includes 'Write more tests'"); | ||
}); | ||
|
||
t.test("teardown", async (t) => { | ||
server.close(); | ||
await cleanupTestPostgresDatabase(sql); | ||
|
||
t.pass(); | ||
}); | ||
}); |
Oops, something went wrong.