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

Add scope to redirects #315

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion admin/src/common/MasterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const pageTreeDocumentTypes = {
Page,
Link,
};
const RedirectsPage = createRedirectsPage();
const RedirectsPage = createRedirectsPage({ scopeParts: ["domain"] });

export const masterMenuData: MasterMenuData = [
{
Expand Down
21 changes: 13 additions & 8 deletions api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ scalar PageContentBlockData @specifiedBy(url: "http://www.ecma-international.org
"""Seo root block data"""
scalar SeoBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")

type RedirectScope {
domain: String!
}

"""Stage root block data"""
scalar StageBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")

Expand Down Expand Up @@ -254,6 +258,7 @@ type Redirect {
generationType: RedirectGenerationType!
createdAt: DateTime!
updatedAt: DateTime!
scope: RedirectScope!
dependencies(offset: Int! = 0, limit: Int! = 25, filter: DependencyFilter, forceRefresh: Boolean! = false): PaginatedDependencies!
}

Expand Down Expand Up @@ -308,6 +313,10 @@ input PageTreeNodeScopeInput {
language: String!
}

input RedirectScopeInput {
domain: String!
}

type Query {
currentUser: CurrentUser!
userPermissionsUserById(id: String!): User!
Expand All @@ -324,10 +333,10 @@ type Query {
pageTreeNodeList(scope: PageTreeNodeScopeInput!, category: String): [PageTreeNode!]!
paginatedPageTreeNodes(scope: PageTreeNodeScopeInput!, category: String, sort: [PageTreeNodeSort!], offset: Int! = 0, limit: Int! = 25): PaginatedPageTreeNodes!
pageTreeNodeSlugAvailable(scope: PageTreeNodeScopeInput!, parentId: ID, slug: String!): SlugAvailability!
redirects(scope: RedirectScopeInput! = {}, query: String, type: RedirectGenerationType, active: Boolean, sortColumnName: String, sortDirection: SortDirection! = ASC): [Redirect!]! @deprecated(reason: "Use paginatedRedirects instead. Will be removed in the next version.")
paginatedRedirects(scope: RedirectScopeInput! = {}, search: String, filter: RedirectFilter, sort: [RedirectSort!], offset: Int! = 0, limit: Int! = 25): PaginatedRedirects!
redirects(scope: RedirectScopeInput!, query: String, type: RedirectGenerationType, active: Boolean, sortColumnName: String, sortDirection: SortDirection! = ASC): [Redirect!]! @deprecated(reason: "Use paginatedRedirects instead. Will be removed in the next version.")
paginatedRedirects(scope: RedirectScopeInput!, search: String, filter: RedirectFilter, sort: [RedirectSort!], offset: Int! = 0, limit: Int! = 25): PaginatedRedirects!
redirect(id: ID!): Redirect!
redirectSourceAvailable(scope: RedirectScopeInput! = {}, source: String!): Boolean!
redirectSourceAvailable(scope: RedirectScopeInput!, source: String!): Boolean!
damItemsList(offset: Int! = 0, limit: Int! = 25, sortColumnName: String, sortDirection: SortDirection! = ASC, scope: DamScopeInput! = {}, folderId: ID, includeArchived: Boolean, filter: DamItemFilterInput): PaginatedDamItems!
damItemListPosition(sortColumnName: String, sortDirection: SortDirection! = ASC, scope: DamScopeInput! = {}, id: ID!, type: DamItemType!, folderId: ID, includeArchived: Boolean, filter: DamItemFilterInput): Int!
damFilesList(offset: Int! = 0, limit: Int! = 25, sortColumnName: String, sortDirection: SortDirection! = ASC, scope: DamScopeInput! = {}, folderId: ID, includeArchived: Boolean = false, filter: FileFilterInput): PaginatedDamFiles!
Expand Down Expand Up @@ -390,10 +399,6 @@ enum SlugAvailability {
Reserved
}

input RedirectScopeInput {
thisScopeHasNoFields____: String
}

input RedirectFilter {
generationType: StringFilter
source: StringFilter
Expand Down Expand Up @@ -481,7 +486,7 @@ type Mutation {
movePageTreeNodesByNeighbour(ids: [ID!]!, input: MovePageTreeNodesByNeighbourInput!): [PageTreeNode!]!
updatePageTreeNodeCategory(id: ID!, category: String!): PageTreeNode!
createPageTreeNode(input: PageTreeNodeCreateInput!, scope: PageTreeNodeScopeInput!, category: String!): PageTreeNode!
createRedirect(scope: RedirectScopeInput! = {}, input: RedirectInput!): Redirect!
createRedirect(scope: RedirectScopeInput!, input: RedirectInput!): Redirect!
updateRedirect(id: ID!, input: RedirectInput!, lastUpdatedAt: DateTime): Redirect!
updateRedirectActiveness(id: ID!, input: RedirectUpdateActivenessInput!): Redirect!
deleteRedirect(id: ID!): Boolean!
Expand Down
3 changes: 2 additions & 1 deletion api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Page } from "@src/documents/pages/entities/page.entity";
import { PagesModule } from "@src/documents/pages/pages.module";
import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope";
import { PageTreeNode } from "@src/page-tree/entities/page-tree-node.entity";
import { RedirectScope } from "@src/redirects/dto/redirect-scope";
import { ValidationError } from "apollo-server-express";
import { Request } from "express";

Expand Down Expand Up @@ -99,7 +100,7 @@ export class AppModule {
Documents: [Page, Link],
Scope: PageTreeNodeScope,
}),
RedirectsModule.register(),
RedirectsModule.register({ Scope: RedirectScope }),
BlobStorageModule.register({
backend: config.blob.storage,
}),
Expand Down
2 changes: 2 additions & 0 deletions api/src/db/migrations/Migration20220721122758.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ export class Migration20220721122758 extends Migration {
'alter table "PageTreeNode" add constraint "PageTreeNode_parentId_foreign" foreign key ("parentId") references "PageTreeNode" ("id") on update cascade on delete set null;',
);
this.addSql('create index "PageTreeNode_parentId_index" on "PageTreeNode" ("parentId");');

this.addSql('alter table "Redirect" add column "scope_domain" text not null;');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is good practice to alter existing migrations. If the starter would be deployed, this change would not be made.

Create a new migration and also make it fail-safe by truncating the table before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#315 (comment)
Please discuss this with @johnnyomair and let me know what I should do ^^

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, this was my suggestion 😅 We did this multiple times in the past to avoid having a bunch of migrations when starting a new project. We could change our approach though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkarnutsch what do you prefer?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nsams what do you think? Maybe we should start adding actually working migrations once the Starter has been deployed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A growing Number of Migrations in New Projects Is Not desirable, that is unnecessarily confusing.

Maybe we can drop database and run fixtures on every starter deploy?

}
}
13 changes: 13 additions & 0 deletions api/src/redirects/dto/redirect-scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Embeddable, Property } from "@mikro-orm/core";
import { Field, InputType, ObjectType } from "@nestjs/graphql";
import { IsString } from "class-validator";

@Embeddable()
@ObjectType("RedirectScope") // name must not be changed in the app
@InputType("RedirectScopeInput") // name must not be changed in the app
export class RedirectScope {
@Property({ columnType: "text" })
@Field()
@IsString()
domain: string;
}
27 changes: 16 additions & 11 deletions site/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { headers } from "next/headers";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { GQLRedirectScope } from "./graphql.generated";
import { createRedirects } from "./redirects/redirects";
import type { PublicSiteConfig } from "./site-configs";

Expand Down Expand Up @@ -70,22 +71,26 @@ export async function middleware(request: NextRequest) {
throw new Error(`Cannot get siteConfig for host ${host}`);
}

const scope = { domain: siteConfig.scope.domain };

if (pathname.startsWith("/dam/")) {
return NextResponse.rewrite(new URL(`${process.env.API_URL_INTERNAL}${request.nextUrl.pathname}`));
}

const redirects = await createRedirects();
if (scope) {
const redirects = await createRedirects(scope);

const redirect = redirects.get(pathname);
if (redirect) {
const destination: string = redirect.destination;
return NextResponse.redirect(new URL(destination, request.url), redirect.permanent ? 308 : 307);
}
const redirect = redirects.get(pathname);
if (redirect) {
const destination: string = redirect.destination;
return NextResponse.redirect(new URL(destination, request.url), redirect.permanent ? 308 : 307);
}

const rewrites = await createRewrites();
const rewrite = rewrites.get(pathname);
if (rewrite) {
return NextResponse.rewrite(new URL(rewrite.destination, request.url));
const rewrites = await createRewrites(scope);
const rewrite = rewrites.get(pathname);
if (rewrite) {
return NextResponse.rewrite(new URL(rewrite.destination, request.url));
}
}

return NextResponse.rewrite(
Expand All @@ -101,7 +106,7 @@ export async function middleware(request: NextRequest) {

type RewritesMap = Map<string, Rewrite>;

async function createRewrites(): Promise<RewritesMap> {
async function createRewrites(scope: Pick<GQLRedirectScope, "domain">): Promise<RewritesMap> {
const rewritesMap = new Map<string, Rewrite>();
return rewritesMap;
}
Expand Down
21 changes: 13 additions & 8 deletions site/src/redirects/redirects.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { gql } from "@comet/cms-site";
import { ExternalLinkBlockData, InternalLinkBlockData, RedirectsLinkBlockData } from "@src/blocks.generated";
import { GQLRedirectScope } from "@src/graphql.generated";
import { createGraphQLFetch } from "@src/util/graphQLClient";
import { RouteHas } from "next/dist/lib/load-custom-routes";

import { memoryCache } from "./cache";
import { GQLRedirectsQuery, GQLRedirectsQueryVariables } from "./redirects.generated";

const redirectsQuery = gql`
query Redirects($filter: RedirectFilter, $sort: [RedirectSort!], $offset: Int!, $limit: Int!) {
paginatedRedirects(filter: $filter, sort: $sort, offset: $offset, limit: $limit) {
query Redirects($scope: RedirectScopeInput!, $filter: RedirectFilter, $sort: [RedirectSort!], $offset: Int!, $limit: Int!) {
paginatedRedirects(scope: $scope, filter: $filter, sort: $sort, offset: $offset, limit: $limit) {
nodes {
sourceType
source
Expand All @@ -33,7 +34,7 @@ const createInternalRedirects = async (): Promise<Map<string, Redirect>> => {
return redirectsMap;
};

async function* fetchApiRedirects() {
async function* fetchApiRedirects(scope: GQLRedirectScope) {
let offset = 0;
const limit = 100;

Expand All @@ -43,6 +44,7 @@ async function* fetchApiRedirects() {
sort: { field: "createdAt", direction: "DESC" },
offset,
limit,
scope,
});

yield* paginatedRedirects.nodes;
Expand All @@ -55,14 +57,14 @@ async function* fetchApiRedirects() {
}
}

const createApiRedirects = async (): Promise<Map<string, Redirect>> => {
const createApiRedirects = async (scope: GQLRedirectScope): Promise<Map<string, Redirect>> => {
const redirects = new Map<string, Redirect>();
function replaceRegexCharacters(value: string): string {
// escape ":" and "?", otherwise it is used for next.js regex path matching (https://nextjs.org/docs/pages/api-reference/next-config-js/redirects#regex-path-matching)
return value.replace(/[:?]/g, "\\$&");
}

for await (const redirect of fetchApiRedirects()) {
for await (const redirect of fetchApiRedirects(scope)) {
let source: string | undefined;
let destination: string | undefined;
let has: Redirect["has"];
Expand Down Expand Up @@ -113,9 +115,12 @@ const createApiRedirects = async (): Promise<Map<string, Redirect>> => {

type Redirect = { destination: string; permanent: boolean; has?: RouteHas[] | undefined };

export const createRedirects = async () => {
const key = `redirects`;
export const createRedirects = async (scope: GQLRedirectScope) => {
const key = `redirects-${JSON.stringify(scope)}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const key = `redirects-${JSON.stringify(scope)}`;
const key = `redirects-${scope.domain}`;

return memoryCache.wrap(key, async () => {
return new Map<string, Redirect>([...Array.from(await createApiRedirects()), ...Array.from(await createInternalRedirects())]);
return new Map<string, Redirect>([
...Array.from(await createApiRedirects({ domain: scope.domain })),
...Array.from(await createInternalRedirects()),
]);
});
};
Loading