Skip to content

Commit

Permalink
add server-only-models doc
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-burel committed Oct 15, 2021
1 parent 33f3469 commit 8fbba54
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 178 deletions.
17 changes: 15 additions & 2 deletions src/components/vn/learn/MultipleChoiceQuestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,21 @@ export const MultipleChoiceQuestion = ({
<Box>
<List
subheader={
<ListSubheader>{question} (click on the right answer)</ListSubheader>
<ListSubheader sx={{ color: "secondary.main" }}>
{question} (click on the right answer)
</ListSubheader>
}
>
{answers.map((answer, idx) => {
return (
<ListItem key={idx}>
<ListItemButton
sx={{
boxShadow: 1,
borderLeft: "3px solid",
borderColor: "secondary.main",
borderRadius: 1,
}}
onClick={() => setCurrentAnswer(idx)}
selected={idx === currentAnswer}
>
Expand All @@ -45,7 +53,12 @@ export const MultipleChoiceQuestion = ({
);
})}
</List>
{currentAnswer === validAnswerIdx ? ifOk : null}
{currentAnswer === validAnswerIdx ? (
<div>
<Typography sx={{ color: "success.main" }}>Right answer!</Typography>
{ifOk}
</div>
) : null}
{currentAnswer && currentAnswer !== validAnswerIdx ? (
<>
<Typography sx={{ color: "warning.main" }}>
Expand Down
1 change: 1 addition & 0 deletions src/components/vn/learn/Steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const steps = [
{ name: "1 - Run", path: "/learn/intro-offline" },
{ name: "2 - Mongo", path: "/learn/mongo" },
{ name: "3 - Models", path: "/learn/about-models" },
{ name: "4 - Server models", path: "/learn/server-only-models" },
{ name: "Done!", path: "/learn/final" },
];
const stepPaths = steps.map((s, idx) => {
Expand Down
45 changes: 16 additions & 29 deletions src/models/sampleModel.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,34 @@ import {
} from "@vulcanjs/graphql/server";
import { createMongooseConnector } from "@vulcanjs/mongo";
import merge from "lodash/merge";
import { modelDef as modelDefShared } from "./sampleModel";
import {
modelDef as commonModelDef,
SampleModelType,
schema as commonSchema,
} from "./sampleModel";

const schema: VulcanGraphqlSchemaServer = {
_id: {
type: String,
optional: true,
canRead: ["guests"],
},
userId: {
type: String,
optional: true,
canRead: ["guests"],
},
createdAt: {
type: Date,
optional: true,
canRead: ["admins"],
onCreate: () => {
return new Date();
},
},
someField: {
const schema: VulcanGraphqlSchemaServer = merge({}, commonSchema, {
// An API-only field, that will appear in the graphql schema
// but not used in the browser
someServerOnlyField: {
type: String,
optional: true,
canRead: ["guests"],
canUpdate: ["admins"],
canCreate: ["owners"],
searchable: true,
},
};
});

export interface SampleModelType extends VulcanDocument {
someField: string;
export interface SampleModelTypeServer extends SampleModelType {
someServerOnlyField: string;
}

const modelDef: CreateGraphqlModelOptionsServer = merge({}, modelDefShared, {
const modelDef: CreateGraphqlModelOptionsServer = merge({}, commonModelDef, {
schema,
// add other server only options here
graphql: {
/* ...*/
},
// add other server only options here,
// like callbacks
graphql: {},
});
export const SampleModel = createGraphqlModelServer(modelDef);

Expand Down
5 changes: 3 additions & 2 deletions src/models/sampleModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
* I am a sample model
* Replace me with your own
*/
import { VulcanSchema, VulcanDocument } from "@vulcanjs/schema";
import { VulcanDocument } from "@vulcanjs/schema";
import {
createGraphqlModel,
CreateGraphqlModelOptionsShared,
VulcanGraphqlSchema,
} from "@vulcanjs/graphql";

const schema: VulcanSchema = {
export const schema: VulcanGraphqlSchema = {
/** Unique id of the document in the database. You'll want to leave this field as is. */
_id: {
type: String,
Expand Down
94 changes: 14 additions & 80 deletions src/models/user.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
import merge from "lodash/merge";

import SimpleSchema from "simpl-schema";
import {
CreateGraphqlModelOptionsServer,
createGraphqlModelServer,
Expand Down Expand Up @@ -80,89 +79,24 @@ const passwordAuthSchema: VulcanGraphqlSchemaServer = {
canCreate: [],
canUpdate: [],
},
};

const schema: VulcanGraphqlSchemaServer = merge({}, clientSchema, {
// _id, userId, and createdAT are basic field you may want to use in almost all schemas
_id: {
type: String,
optional: true,
canRead: ["guests"],
},
// userId is the _id of the owner of the document
// Here, it guarantees that the user belongs to group "owners" for his own data
userId: {
type: String,
optional: true,
canRead: ["guests"],
},
createdAt: {
type: Date,
optional: true,
canRead: ["admins"],
onCreate: () => {
return new Date();
},
},
username: {
// Example of a custom field resolver to get data from other API
/*
twitterId: {
type: String,
optional: true,
canRead: ["guests"],
canUpdate: ["admins"],
canCreate: ["owners"],
searchable: true,
},
isAdmin: {
type: Boolean,
label: "Admin",
input: "checkbox",
optional: true,
canRead: ["admins", "owners"],
canCreate: ["admins"],
canUpdate: ["admins"],
canRead: ["guests"],
},

email: {
type: String,
optional: false,
regEx: SimpleSchema.RegEx.Email,
// mustComplete: true,
input: "text",
canCreate: ["members"],
canUpdate: ["owners", "admins"],
canRead: ["owners", "admins"],
searchable: true,
unique: true,
// unique: true // note: find a way to fix duplicate accounts before enabling this
},
groups: {
type: Array,
optional: true,
input: "checkboxgroup",
canCreate: ["admins"],
canUpdate: ["admins"],
canRead: ["guests"],
// TODO: allow to manage custom groups
// form: {
// options: function () {
// const groups = _.without(
// _.keys(getCollection("Users").groups),
// "guests",
// "members",
// "owners",
// "admins"
// );
// return groups.map((group) => {
// return { value: group, label: group };
// });
// },
// },
},
"groups.$": {
type: String,
optional: true,
},
resolveAs: {
type: "JSON",
resolver:(root, args, context) => {
return {twiterHandle: "@VulcanJS"}
}
}
}
*/
};

const schema: VulcanGraphqlSchemaServer = merge({}, clientSchema, {
...passwordAuthSchema,
} as VulcanGraphqlSchemaServer);

Expand Down
2 changes: 1 addition & 1 deletion src/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VulcanDocument, VulcanSchema } from "@vulcanjs/schema";
import { VulcanDocument } from "@vulcanjs/schema";
import SimpleSchema from "simpl-schema";
import {
createGraphqlModel,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/learn/about-models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ First, please answer this question:
answers={[models.length.toString(), (models.length + 1).toString()]}
validAnswerIdx={0}
ifOk={
<Button variant="contained" href="/learn/final">
<Button variant="contained" href="/learn/server-only-models">
Click to go to next step
</Button>
}
Expand Down
107 changes: 44 additions & 63 deletions src/pages/learn/server-only-models.mdx
Original file line number Diff line number Diff line change
@@ -1,84 +1,65 @@
import { Button } from "@mui/material";
import { LearnLayout } from "~/components/vn/learn/LearnLayout";
import { StepIfElse } from "~/components/vn/learn/Steps";
import { MultipleChoiceQuestion } from "~/components/vn/learn/MultipleChoiceQuestion";

# Step 2: Plug the database
# Step 4: Server-side models

Vulcan is a **full-stack** framework. It means that it provides a way to create user interfaces (front-end) and to manage a web server (back-end).
Most back-end need to manipulate data, using a database, like [MongoDB](https://www.mongodb.com/).
This is what we sometimes call the ["3-tier architecture"](https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture).
## Why you need server-only code

Therefore, for Vulcan to work, you need to setup a Mongo database on your computer.
Vulcan models are an **isomorphic** feature.
It means that models work both client-side, for the front-end user interface,
and server-side, for the backend business logic.

## Prerequesites
That makes sense, because models represent real-life objects.
Whether you are creating a form or an API endpoint,
a blog Author is still an Author,
so it's a good practice to share as much code as possible.

- Install Docker ([link for Ubuntu](https://docs.docker.com/engine/install/ubuntu/), [link for Mac](https://docs.docker.com/desktop/mac/install/)).
- (Optional) Install [Compass](https://www.mongodb.com/fr-fr/download-center/compass), the official tool to visualize your data
However, some code should stay private.
For example, the code that process user password belongs to the server.
It should not be included in the client bundle and shared to users.

## Run Mongo
## Discover the User model server code

If Docker is installed, this magic command will either install and run, or just run Mongo on your computer.
The User model is a good example of server-only features.

```sh
yarn run mongo
```
Open `src/models/user.server.ts`.

You should already be able to access the database in Compass, using the following URI:
You'll see:

```
mongodb://localhost:27017/vulcan-next-app
```
- **Model specific logic**: a bunch of functions related to users
- **Server-only fields**: fields you **don't** want to see in the user interface
- **Server callbacks**: functions that are called on certain events (creation, update, deletion...),
like sending an email on success, modifying data before storing them etc.
- **Custom field-resolvers**: some data might **not** be stored outside of your database.
For instance, you might want to get information about the user from the Twitter API.
To do so, you can use a custom field resolver, with the `resolveAs` property.

## Link Mongo to your Vulcan application
It is a very advanced model: it handles user signup and authentication.
Since those features are related to security, you want them to
happen server-side.

Now, you'll want your Vulcan Next app to connect to this database.
Hopefully, your models will be simpler than that most of the time!

To do so, you need to configure a global environment variable, `MONGO_URI`.
We are going to use [Next.js configuration features](https://blog.vulcanjs.org/how-to-set-configuration-variables-in-next-js-a81505e43dad).
## Go to step 5

```sh
# Duplicate ".env.development" to create a ".env.development.local"
cp .env.development .env.development.local
```
First, please answer this question:

You can change the values of this `.env.development.local`: it
will only exist on your own computer, so you can even put password
and secret values here.
<MultipleChoiceQuestion
question="What does the User model do when creating a new user?"
answers={[
"It hashes the provided password before storing it, using the 'create' callback.",
"It sends an email to the moderators.",
]}
validAnswerIdx={0}
ifOk={
<Button variant="contained" href="/learn/final">
Click to go to next step
</Button>
}
/>

You'll find a line like this:

```
MONGO_URI="mongodb+srv://johnDoe:[email protected]/sample_restaurants?retryWrites=true&w=majority"
# MONGO_URI="mongodb://localhost:27017/vulcan-next-app"
```

You can comment the first value, and remove the second one:

```
MONGO_URI="mongodb://localhost:27017/vulcan-next-app"
```

Now, Vulcan Next will use your local database.

**Restart your server for this change to apply (ctrl+C in your terminal and then `yarn run dev`).**

## Go to step 3

If this button is still disabled:

- check if you Mongo server is running correctly
- restart Vulcan Next (ctrl+C in your terminal, and `yarn run dev` again)

<StepIfElse
step={3}
ifOk={<Button variant="contained">Click to go to next step</Button>}
ifNotOk={
<Button variant="contained" disabled={true}>
Please complete step 2 to activate this button
</Button>
}
></StepIfElse>

export default function MongoPage(props) {
export default function ServerOnlyModels(props) {
return <LearnLayout {...props} />;
}

0 comments on commit 8fbba54

Please sign in to comment.