Skip to content

Commit 3bca166

Browse files
authored
feat(response): allow to use a route identifier to redirect (#16)
1 parent 26f031a commit 3bca166

File tree

9 files changed

+482
-173
lines changed

9 files changed

+482
-173
lines changed

adonis-typings/response.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
declare module '@ioc:Adonis/Core/Response' {
1111
import { ServerResponse, IncomingMessage } from 'http'
1212
import { MacroableConstructorContract } from 'macroable'
13+
import { MakeUrlOptions } from '@ioc:Adonis/Core/Route'
1314
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
1415
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
1516

@@ -106,7 +107,9 @@ declare module '@ioc:Adonis/Core/Response' {
106107
): void
107108

108109
location (url: string): this
109-
redirect (url: string, reflectQueryParams?: boolean, statusCode?: number): void
110+
111+
redirect (): RedirectContract
112+
redirect (path: string, forwardQueryString?: boolean, statusCode?: number): void
110113

111114
cookie (key: string, value: any, options?: Partial<CookieOptions>): this
112115
plainCookie (key: string, value: any, options?: Partial<CookieOptions>): this
@@ -166,6 +169,20 @@ declare module '@ioc:Adonis/Core/Response' {
166169
httpVersionNotSupported (body: any, generateEtag?: boolean): void
167170
}
168171

172+
/**
173+
* Redirect interface
174+
*/
175+
export interface RedirectContract {
176+
status (statusCode: number): this
177+
withQs (): this
178+
withQs (values: { [key: string]: any }): this
179+
withQs (name: string, value: any): this
180+
181+
back (): void
182+
toRoute (routeIdentifier: string, urlOptions?: MakeUrlOptions, domain?: string): void
183+
toPath (url: string): void
184+
}
185+
169186
/**
170187
* Config accepted by response the class
171188
*/

package-lock.json

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@types/node": "^13.13.0",
4343
"@types/pluralize": "0.0.29",
4444
"@types/proxy-addr": "^2.0.0",
45+
"@types/qs": "^6.9.2",
4546
"@types/supertest": "^2.0.8",
4647
"autocannon": "^4.6.0",
4748
"commitizen": "^4.0.4",
@@ -109,7 +110,7 @@
109110
"on-finished": "^2.3.0",
110111
"pluralize": "^8.0.0",
111112
"proxy-addr": "^2.0.6",
112-
"qs": "^6.9.3",
113+
"qs": "^6.9.4",
113114
"quick-lru": "^5.1.0",
114115
"type-is": "^1.6.18",
115116
"vary": "^1.1.2"

src/HttpContext/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Socket } from 'net'
1313
import { inspect } from 'util'
1414
import proxyAddr from 'proxy-addr'
1515
import { Macroable } from 'macroable'
16-
import { RouteNode } from '@ioc:Adonis/Core/Route'
16+
import { RouteNode, RouterContract } from '@ioc:Adonis/Core/Route'
1717
import { ServerConfig } from '@ioc:Adonis/Core/Server'
1818
import { IncomingMessage, ServerResponse } from 'http'
1919
import { LoggerContract } from '@ioc:Adonis/Core/Logger'
@@ -76,6 +76,7 @@ export class HttpContext extends Macroable implements HttpContextContract {
7676
logger: LoggerContract,
7777
profiler: ProfilerRowContract,
7878
encryption: EncryptionContract,
79+
router: RouterContract,
7980
req?: IncomingMessage,
8081
res?: ServerResponse,
8182
serverConfig?: ServerConfig,
@@ -120,7 +121,7 @@ export class HttpContext extends Macroable implements HttpContextContract {
120121
etag: serverConfig.etag,
121122
cookie: serverConfig.cookie,
122123
jsonpCallbackName: serverConfig.jsonpCallbackName,
123-
})
124+
}, router)
124125

125126
/**
126127
* Creating new ctx instance

src/Redirect/index.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* @adonisjs/http-server
3+
*
4+
* (c) Harminder Virk <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
/// <reference path="../../adonis-typings/index.ts" />
11+
12+
import { parse } from 'url'
13+
import { stringify } from 'qs'
14+
import encodeurl from 'encodeurl'
15+
import { IncomingMessage } from 'http'
16+
17+
import { RedirectContract, ResponseContract } from '@ioc:Adonis/Core/Response'
18+
import { RouterContract, MakeUrlOptions } from '@ioc:Adonis/Core/Route'
19+
20+
export class Redirect implements RedirectContract {
21+
private forwardQueryString = false
22+
private statusCode = 302
23+
private queryString: { [key: string]: any } = {}
24+
25+
constructor (
26+
private request: IncomingMessage,
27+
private response: ResponseContract,
28+
private router: RouterContract
29+
) {
30+
}
31+
32+
/**
33+
* Set a custom status code.
34+
*/
35+
public status (statusCode: number): this {
36+
this.statusCode = statusCode
37+
return this
38+
}
39+
40+
/**
41+
* Forward the current QueryString or define one.
42+
*/
43+
public withQs (): this
44+
public withQs (values: { [key: string]: any }): this
45+
public withQs (name: string, value: any): this
46+
public withQs (name?: { [key: string]: any } | string, value?: any): this {
47+
if (typeof name === 'undefined') {
48+
this.forwardQueryString = true
49+
return this
50+
}
51+
52+
if (typeof name === 'string') {
53+
this.queryString[name] = value
54+
return this
55+
}
56+
57+
this.queryString = name
58+
return this
59+
}
60+
61+
/**
62+
* Redirect to the previous path.
63+
*/
64+
public back () {
65+
const url = (this.request.headers['referer'] || this.request.headers['referrer'] || '/') as string
66+
67+
return this.toPath(url)
68+
}
69+
70+
/**
71+
* Redirect the request using a route identifier.
72+
*/
73+
public toRoute (routeIdentifier: string, urlOptions?: MakeUrlOptions, domain?: string) {
74+
const route = this.router.lookup(routeIdentifier, domain)
75+
76+
if (!route) {
77+
throw new Error(`Unable to lookup route for "${routeIdentifier}" identifier`)
78+
}
79+
80+
const url = this.router.makeUrl(routeIdentifier, urlOptions, domain) as string
81+
return this.toPath(url)
82+
}
83+
84+
/**
85+
* Redirect the request using a path.
86+
*/
87+
public toPath (url: string) {
88+
let query
89+
90+
// Extract the current QueryString if we want to forward it.
91+
if (this.forwardQueryString) {
92+
const { query: extractedQuery } = parse(this.request.url!, false)
93+
query = extractedQuery
94+
}
95+
96+
// If we define our own QueryString, use it instead of the one forwarded.
97+
if (Object.keys(this.queryString).length > 0) {
98+
query = stringify(this.queryString)
99+
}
100+
101+
url = query ? `${url}?${query}` : url
102+
this.response.location(encodeurl(url))
103+
this.response.safeStatus(this.statusCode)
104+
this.response.type('text/plain; charset=utf-8')
105+
this.response.send(`Redirecting to ${url}`)
106+
}
107+
}

src/Response/index.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
import etag from 'etag'
1313
import vary from 'vary'
1414
import fresh from 'fresh'
15-
import { parse } from 'url'
1615
import mime from 'mime-types'
1716
import destroy from 'destroy'
1817
import { extname } from 'path'
19-
import encodeurl from 'encodeurl'
2018
import onFinished from 'on-finished'
2119
import { Macroable } from 'macroable'
2220
import { Exception } from '@poppinss/utils'
@@ -31,12 +29,15 @@ import {
3129
ResponseConfig,
3230
ResponseStream,
3331
ResponseContract,
32+
RedirectContract,
3433
ResponseContentType,
3534
} from '@ioc:Adonis/Core/Response'
3635

36+
import { RouterContract } from '@ioc:Adonis/Core/Route'
3737
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
3838
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
3939

40+
import { Redirect } from '../Redirect'
4041
import { CookieSerializer } from '../Cookie/Serializer'
4142

4243
/**
@@ -136,6 +137,7 @@ export class Response extends Macroable implements ResponseContract {
136137
public response: ServerResponse,
137138
private encryption: EncryptionContract,
138139
private config: ResponseConfig,
140+
private router: RouterContract
139141
) {
140142
super()
141143
}
@@ -821,24 +823,37 @@ export class Response extends Macroable implements ResponseContract {
821823
}
822824

823825
/**
824-
* Redirect request to a different URL. Current request `query string` can be forwared
825-
* by setting 2nd param to `true`.
826+
* Redirect the request.
827+
*
828+
* @example
829+
* ```js
830+
* response.redirect('/foo')
831+
* response.redirect().toRoute('foo.bar')
832+
* response.redirect().back()
833+
* ```
826834
*/
835+
public redirect (): RedirectContract
836+
public redirect (path: string, forwardQueryString?: boolean, statusCode?: number): void
827837
public redirect (
828-
url: string,
829-
sendQueryParams?: boolean,
830-
statusCode: number = 302,
831-
): void {
832-
url = url === 'back'
833-
? (this.request.headers['referer'] || this.request.headers['referrer'] || '/') as string
834-
: url
835-
836-
const { query } = parse(this.request.url!, false)
837-
url = sendQueryParams && query ? `${url}?${query}` : url
838-
this.location(encodeurl(url))
839-
this.safeStatus(statusCode || 302)
840-
this.type('text/plain; charset=utf-8')
841-
this.send(`Redirecting to ${url}`)
838+
path?: string,
839+
forwardQueryString: boolean = false,
840+
statusCode: number = 302
841+
): RedirectContract | void {
842+
const handler = new Redirect(this.request, this, this.router)
843+
844+
if (forwardQueryString) {
845+
handler.withQs()
846+
}
847+
848+
if (path === 'back') {
849+
return handler.status(statusCode).back()
850+
}
851+
852+
if (path) {
853+
return handler.status(statusCode).toPath(path)
854+
}
855+
856+
return handler
842857
}
843858

844859
/**

src/Server/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export class Server implements ServerContract {
153153
}
154154

155155
const request = new Request(req, res, this.encryption, this.httpConfig)
156-
const response = new Response(req, res, this.encryption, this.httpConfig)
156+
const response = new Response(req, res, this.encryption, this.httpConfig, this.router)
157157

158158
const requestAction = this.getProfilerRow(request)
159159
const ctx = this.getContext(request, response, requestAction)

0 commit comments

Comments
 (0)