Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add session, cookies & validation pages #30

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .gflowrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"flow": "gflow",
"remote": "origin",
"develop": "main",
"production": "main",
"ignores": [],
"syncAfterFinish": false,
"postFinish": "",
"skipTest": false,
"charReplacement": "-",
"charBranchNameSeparator": "-",
"branchTypes": {
"feat": "feat",
"fix": "fix",
"chore": "chore",
"docs": "docs"
},
"refs": {}
}
92 changes: 89 additions & 3 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default defineConfig({
},
{
text: "Session & Cookies",
link: `/tutorials/session`
link: `/docs/session`
},
{
text: "Testing",
Expand Down Expand Up @@ -288,8 +288,94 @@ export default defineConfig({
]
},
{
text: "Controllers",
link: "/docs/controllers"
test: "Fundamentals",
items: [
{
text: "Controllers",
link: "/docs/controllers"
},
{
text: "Providers",
link: `/docs/providers`
},
{
text: "Models",
link: `/docs/model`
},
{
text: "Json Mapper",
link: `/docs/json-mapper`
},
{
text: "Middlewares",
link: `/docs/middlewares`
},
{
text: "Pipes",
link: `/docs/pipes`
},
{
text: "Interceptors",
link: `/docs/interceptors`
},
{
text: "Authentication",
link: `/docs/authentication`
},
{
text: "Hooks",
link: `/docs/hooks`
},
{
text: "Response filter",
link: `/docs/response-filter`
},
{
text: "Exceptions",
link: `/docs/exceptions`
},
{
text: "Logger",
link: `/docs/logger`
},
{
text: "Context",
link: `/docs/request-context`
},
{
text: "Testing",
link: `/docs/testing`
}
]
},
{
text: "Advanced",
items: [
{
text: "Cache",
link: `/docs/cache`
},
{
text: "Platform API",
link: `/docs/platform-api`
},
{
text: "Command",
link: `/docs/command`
},
{
text: "Templating",
link: `/docs/templating`
},
{
text: "Validation",
link: `/docs/validation`
},
{
text: "Session & Cookies",
link: `/docs/session`
}
]
}
]
},
Expand Down
109 changes: 109 additions & 0 deletions docs/docs/session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Session & cookies

Ts.ED provides two decorators to get @@Session@@ and @@Cookies@@ values in your controller.

## Installation

Before using the Session and Cookies, we need to install a module like [express-session](https://www.npmjs.com/package/express-session) but
you can use another module which follows the same convention.

::: code-group

```bash [Express.js]
npm install --save express-session
```

```bash [Koa.js]
npm install --save koa-session
```

:::

::: warning
The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.

For a list of stores, see [compatible session stores](https://www.npmjs.com/package/express-session#compatible-session-stores).
:::

## Configuration

Edit your Server and add these lines:

<<< @/docs/snippets/session/configuration.ts

## Usage

### Session

#### Get value

<<< @/docs/snippets/session/example-session.ts

#### Set value

```typescript
import {BodyParams, Controller, Post, Session} from "@tsed/common";
import {Returns} from "@tsed/schema";

@Controller("/")
export class MyCtrl {
@Post("/")
updateSession(@Session() session: any) {
session.count = (session.count || 0) + 1;
return "OK - " + session.count;
}
}
```

### Cookies

#### Get value

<<< @/docs/snippets/session/example-cookies.ts

#### Set value

```typescript
import {BodyParams, Controller, Post, Cookies} from "@tsed/common";
import {Returns} from "@tsed/schema";

@Controller("/")
export class MyCtrl {
@Post("/")
updateSession(@Cookies() cookies: any) {
cookies.count = (cookies.count || 0) + 1;
return "OK - " + cookies.count;
}
}
```

## Initialize session

Sometimes we want to be sure that the session is correctly initialized with the right values.

Let's start by creating a middleware CreateRequestSessionMiddleware in `middlewares` directory:

<<< @/docs/snippets/session/example-create-session.ts

Then, add this middleware on the server:

<<< @/docs/snippets/session/configuration-middleware.ts

Finally, you can read and write values in your controller:

<<< @/docs/snippets/session/example-session.ts

In addition, you can add integration tests with SuperTest and `@tsed/testing` package.
Here is an example of Rest API test:

::: code-group

<<< @/docs/snippets/session/example-test.jest.ts [Jest]
<<< @/docs/snippets/session/example-test.vitest.ts [Vitest]
<<< @/docs/snippets/session/example-test.mocha.ts [Mocha]

:::

::: tip
You can find a working example on [Express Session here](https://github.com/tsedio/tsed-example-session).
:::
33 changes: 33 additions & 0 deletions docs/docs/snippets/session/configuration-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {PlatformApplication} from "@tsed/common";
import {Configuration, Inject} from "@tsed/di";
import "@tsed/platform-express";
import compress from "compression";
import cookieParser from "cookie-parser";
import session from "express-session";
import methodOverride from "method-override";
import {CreateRequestSessionMiddleware} from "./middlewares/CreateRequestSessionMiddleware";

@Configuration({
middlewares: [
cookieParser(),
compress({}),
methodOverride(),
session({
secret: "keyboard cat", // change secret key
resave: false,
saveUninitialized: true,
cookie: {
secure: false // set true if HTTPS is enabled
}
}),
CreateRequestSessionMiddleware
]
})
class Server {
@Inject()
app: PlatformApplication;

public $beforeRoutesInit(): void | Promise<any> {
this.app.getApp().set("trust proxy", 1); // trust first proxy
}
}
29 changes: 29 additions & 0 deletions docs/docs/snippets/session/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {PlatformApplication} from "@tsed/common";
import {Configuration, Inject} from "@tsed/di";
import "@tsed/platform-express";
import compress from "compression";
import cookieParser from "cookie-parser";
import session from "express-session";
import methodOverride from "method-override";

@Configuration({
middlewares: [
cookieParser(),
compress(),
methodOverride(),
session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: {secure: true}
})
]
})
export class Server {
@Inject()
app: PlatformApplication;

public $beforeRoutesInit(): void | Promise<any> {
this.app.getApp().set("trust proxy", 1); // trust first proxy
}
}
22 changes: 22 additions & 0 deletions docs/docs/snippets/session/example-cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Cookies} from "@tsed/platform-params";
import {Post} from "@tsed/schema";
import {Controller} from "@tsed/di";
import {IUser} from "./interfaces/IUser";

@Controller("/")
class MyCtrl {
@Post("/")
getCookies(@Cookies() cookies: any) {
console.log("Entire cookies", cookies);
}

@Post("/")
getIdInCookies(@Cookies("id") id: string) {
console.log("ID", id);
}

@Post("/")
getObjectInCookies(@Cookies("user") user: IUser) {
console.log("user", user);
}
}
13 changes: 13 additions & 0 deletions docs/docs/snippets/session/example-create-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Req} from "@tsed/common";
import {Middleware} from "@tsed/platform-middlewares";

@Middleware()
export class CreateRequestSessionMiddleware {
use(@Req() request: Req) {
if (request.session) {
request.session.user = request.session.user || {
id: null
};
}
}
}
28 changes: 28 additions & 0 deletions docs/docs/snippets/session/example-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Session, BodyParams} from "@tsed/platform-params";
import {Get, Post} from "@tsed/schema";
import {Controller} from "@tsed/di";
import {Returns} from "@tsed/schema";

@Controller("/")
export class MyCtrl {
@Get("/whoami")
whoAmI(@Session() session: any) {
console.log("User in session =>", session.user);

return session.user && session.user.id ? `Hello user ${session.user.name}` : "Hello world";
}

@Post("/login")
@Returns(204)
login(@BodyParams("name") name: string, @Session("user") user: any) {
user.id = "1";
user.name = name;
}

@Post("/logout")
@Returns(204)
logout(@Session("user") user: any) {
user.id = null;
delete user.name;
}
}
29 changes: 29 additions & 0 deletions docs/docs/snippets/session/example-test.jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {PlatformTest} from "@tsed/common";
import * as SuperTest from "supertest";
import {Server} from "../../../src/Server";

describe("Session", () => {
beforeAll(PlatformTest.bootstrap(Server));
afterAll(PlatformTest.reset);

describe("Login / Logout", () => {
it("should create session return hello world and connect a fake user", async () => {
const request = SuperTest.agent(PlatformTest.callback());
// WHEN
const response1 = await request.get("/rest/whoami").expect(200);

await request.post("/rest/login").send({name: "UserName"}).expect(204);

const response2 = await request.get("/rest/whoami").expect(200);

await request.post("/rest/logout").expect(204);

const response3 = await request.get("/rest/whoami").expect(200);

// THEN
expect(response1.text).toEqual("Hello world");
expect(response2.text).toEqual("Hello user UserName");
expect(response3.text).toEqual("Hello world");
});
});
});
Loading
Loading