diff --git a/src/components/vn/learn/MultipleChoiceQuestion.tsx b/src/components/vn/learn/MultipleChoiceQuestion.tsx index 05ae5996..7f4b3a2d 100644 --- a/src/components/vn/learn/MultipleChoiceQuestion.tsx +++ b/src/components/vn/learn/MultipleChoiceQuestion.tsx @@ -29,13 +29,21 @@ export const MultipleChoiceQuestion = ({ {question} (click on the right answer) + + {question} (click on the right answer) + } > {answers.map((answer, idx) => { return ( setCurrentAnswer(idx)} selected={idx === currentAnswer} > @@ -45,7 +53,12 @@ export const MultipleChoiceQuestion = ({ ); })} - {currentAnswer === validAnswerIdx ? ifOk : null} + {currentAnswer === validAnswerIdx ? ( +
+ Right answer! + {ifOk} +
+ ) : null} {currentAnswer && currentAnswer !== validAnswerIdx ? ( <> diff --git a/src/components/vn/learn/Steps.tsx b/src/components/vn/learn/Steps.tsx index a3a3e01b..13170d9e 100644 --- a/src/components/vn/learn/Steps.tsx +++ b/src/components/vn/learn/Steps.tsx @@ -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) => { diff --git a/src/models/sampleModel.server.ts b/src/models/sampleModel.server.ts index 9643447b..e3c6c940 100644 --- a/src/models/sampleModel.server.ts +++ b/src/models/sampleModel.server.ts @@ -10,28 +10,16 @@ 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"], @@ -39,18 +27,17 @@ const schema: VulcanGraphqlSchemaServer = { 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); diff --git a/src/models/sampleModel.ts b/src/models/sampleModel.ts index 1a2ca1a6..cf2490da 100644 --- a/src/models/sampleModel.ts +++ b/src/models/sampleModel.ts @@ -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, diff --git a/src/models/user.server.ts b/src/models/user.server.ts index ed2cb85b..bde527da 100644 --- a/src/models/user.server.ts +++ b/src/models/user.server.ts @@ -3,7 +3,6 @@ */ import merge from "lodash/merge"; -import SimpleSchema from "simpl-schema"; import { CreateGraphqlModelOptionsServer, createGraphqlModelServer, @@ -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); diff --git a/src/models/user.ts b/src/models/user.ts index 643bf4af..4f8ae43c 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,4 +1,4 @@ -import { VulcanDocument, VulcanSchema } from "@vulcanjs/schema"; +import { VulcanDocument } from "@vulcanjs/schema"; import SimpleSchema from "simpl-schema"; import { createGraphqlModel, diff --git a/src/pages/learn/about-models.mdx b/src/pages/learn/about-models.mdx index 7bd9cdbf..4807c207 100644 --- a/src/pages/learn/about-models.mdx +++ b/src/pages/learn/about-models.mdx @@ -97,7 +97,7 @@ First, please answer this question: answers={[models.length.toString(), (models.length + 1).toString()]} validAnswerIdx={0} ifOk={ - } diff --git a/src/pages/learn/server-only-models.mdx b/src/pages/learn/server-only-models.mdx index 348e03e6..e1e4f992 100644 --- a/src/pages/learn/server-only-models.mdx +++ b/src/pages/learn/server-only-models.mdx @@ -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. + + Click to go to next step + + } +/> -You'll find a line like this: - -``` -MONGO_URI="mongodb+srv://johnDoe:T74OcxqL15TRt7Zn@lbke-demo-ara2d.mongodb.net/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) - - Click to go to next step} - ifNotOk={ - - } - > - -export default function MongoPage(props) { +export default function ServerOnlyModels(props) { return ; }