Skip to content

Commit

Permalink
Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
cskrov committed Aug 28, 2024
1 parent 69ece65 commit 5655605
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 18 deletions.
13 changes: 13 additions & 0 deletions nais/nais.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ spec:
enabled: true
sidecar:
enabled: true
autoLogin: true
autoLoginIgnorePaths:
- /assets/**
- /
- /nb/klage/**
- /nn/klage/**
- /en/klage/**
- /nb/anke/**
- /nn/anke/**
- /en/anke/**
- /nb/ettersendelse/**
- /nn/ettersendelse/**
- /en/ettersendelse/**
prometheus:
enabled: true
accessPolicy:
Expand Down
3 changes: 2 additions & 1 deletion server/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { isLocal } from '@app/config/env';
import { requiredEnvJson, requiredEnvString } from '@app/config/env-var';
import type { JWK } from 'jose';

export const API_CLIENT_IDS = ['klage-dittnav-api', 'klage-kodeverk-api'];
export const KLAGE_DITTNAV_API = 'klage-dittnav-api';
export const API_CLIENT_IDS = [KLAGE_DITTNAV_API, 'klage-kodeverk-api'];

const cwd = process.cwd(); // This will be the server folder, as long as the paths in the NPM scripts are not changed.
const serverDirectoryPath = cwd;
Expand Down
4 changes: 4 additions & 0 deletions server/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ export const URL: string = getEnvironmentVersion(LOCAL_URL, DEV_URL, PROD_URL);
export const NAIS_NAMESPACE = requiredEnvString('NAIS_NAMESPACE', 'none');

export const POD_NAME = requiredEnvString('OTEL_RESOURCE_ATTRIBUTES_POD_NAME', 'none');

export const YTELSE_OVERVIEW_URL = isDeployedToProd
? 'https://www.nav.no/klage'
: 'https://www.ekstern.dev.nav.no/klage';
4 changes: 1 addition & 3 deletions server/src/plugins/not-found.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isDeployedToProd } from '@app/config/env';
import { isDeployedToProd, YTELSE_OVERVIEW_URL } from '@app/config/env';
import { INNSENDINGSYTELSER } from '@app/innsendingsytelser';
import { getLogger } from '@app/logger';
import { externalRedirectCounter } from '@app/plugins/serve-index/counters';
Expand Down Expand Up @@ -33,8 +33,6 @@ export const notFoundPlugin = fastifyPlugin(
{ fastify: '4', name: NOT_FOUND_PLUGIN_ID, dependencies: [SERVE_INDEX_PLUGIN_ID] },
);

const YTELSE_OVERVIEW_URL = isDeployedToProd ? 'https://www.nav.no/klage' : 'https://www.ekstern.dev.nav.no/klage';

const getCells = (ytelse: string): string[] =>
CASE_TYPES.flatMap((type) => [
createCell(`/nb/${type}/${ytelse}`, type),
Expand Down
31 changes: 25 additions & 6 deletions server/src/plugins/serve-index/get-paths.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'bun:test';
import { getPaths } from './get-paths';
import { getAnonymousPaths, getLoggedInPaths } from './get-paths';

const URLS = [
const ANONYMOUS_PATHS = [
'/nb/klage/DAGPENGER',
'/nb/klage/DAGPENGER',
'/nb/klage/DAGPENGER',
Expand All @@ -26,27 +26,46 @@ const URLS = [
'/nb/ettersendelse/anke/DAGPENGER/begrunnelse',
'/nb/ettersendelse/anke/DAGPENGER/oppsummering',
'/nb/ettersendelse/anke/DAGPENGER/innsending',
];

const LOGGED_IN_PATHS = [
'/nb/sak/:id/begrunnelse',
'/nb/sak/:id/oppsummering',
'/nb/sak/:id/innsending',
'/nb/sak/:id/kvittering',
'/nn/sak/:id/begrunnelse',
'/nn/sak/:id/oppsummering',
'/nn/sak/:id/innsending',
'/nn/sak/:id/kvittering',
'/en/sak/:id/begrunnelse',
'/en/sak/:id/oppsummering',
'/en/sak/:id/innsending',
'/en/sak/:id/kvittering',
];

describe('generate paths', () => {
it('should include all known paths', async () => {
expect.assertions(URLS.length + 1);
it('should include all known anonymous paths', async () => {
expect.assertions(ANONYMOUS_PATHS.length + 1);

const paths = getAnonymousPaths();

// No duplicates.
expect(paths).toBeArrayOfSize(new Set(paths).size);

for (const url of ANONYMOUS_PATHS) {
expect(paths).toContain(url);
}
});

it('should include all known logged in paths', async () => {
expect.assertions(LOGGED_IN_PATHS.length + 1);

const paths = getPaths();
const paths = getLoggedInPaths();

// No duplicates.
expect(paths).toBeArrayOfSize(new Set(paths).size);

for (const url of URLS) {
for (const url of LOGGED_IN_PATHS) {
expect(paths).toContain(url);
}
});
Expand Down
10 changes: 9 additions & 1 deletion server/src/plugins/serve-index/get-paths.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { INNSENDINGSYTELSER } from '@app/innsendingsytelser';
import { CASE_TYPES, LANGUAGES, RECEIPT_STEP, STEPS } from '@app/plugins/serve-index/segments';

export const getPaths = (): string[] => {
export const getAnonymousPaths = (): string[] => {
const paths: string[] = [];

for (const lang of LANGUAGES) {
Expand All @@ -16,7 +16,15 @@ export const getPaths = (): string[] => {
}
}
}
}

return paths;
};

export const getLoggedInPaths = (): string[] => {
const paths: string[] = [];

for (const lang of LANGUAGES) {
paths.push(`/${lang}/sak/:id`);

for (const step of STEPS) {
Expand Down
54 changes: 54 additions & 0 deletions server/src/plugins/serve-index/parse-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { type Language, isLanguage } from '@app/plugins/serve-index/segments';

enum ApiCaseType {
KLAGE = 'KLAGE',
KLAGE_ETTERSENDELSE = 'KLAGE_ETTERSENDELSE',
ANKE = 'ANKE',
ANKE_ETTERSENDELSE = 'ANKE_ETTERSENDELSE',
}

interface ParsedPath {
lang: Language;
type: ApiCaseType;
innsendingsytelse: string;
}

export const parsePath = (path: string): ParsedPath | null => {
// /nb/ettersendelse/klage/ytelse
// /nb/klage/ytelse
const [lang, a, b, c] = path.split('/').slice(1);

if (lang === undefined || !isLanguage(lang)) {
return null;
}

if (a === 'ettersendelse') {
if (c === undefined) {
return null;
}

if (b === 'klage') {
return { lang, type: ApiCaseType.KLAGE_ETTERSENDELSE, innsendingsytelse: c };
}

if (b === 'anke') {
return { lang, type: ApiCaseType.ANKE_ETTERSENDELSE, innsendingsytelse: c };
}

return null;
}

if (b === undefined) {
return null;
}

if (a === 'klage') {
return { lang, type: ApiCaseType.KLAGE, innsendingsytelse: b };
}

if (a === 'anke') {
return { lang, type: ApiCaseType.ANKE, innsendingsytelse: b };
}

return null;
};
6 changes: 4 additions & 2 deletions server/src/plugins/serve-index/segments.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
enum Language {
export enum Language {
NB = 'nb',
NN = 'nn',
EN = 'en',
}

export const LANGUAGES = Object.values(Language);

export const isLanguage = (value: string): value is Language => LANGUAGES.some((lang) => lang === value);

enum CaseType {
KLAGE = 'klage',
ANKE = 'anke',
}

export const CASE_TYPES = Object.values(CaseType);

enum Step {
export enum Step {
BEGRUNNELSE = 'begrunnelse',
OPPSUMMERING = 'oppsummering',
INNSENDING = 'innsending',
Expand Down
115 changes: 110 additions & 5 deletions server/src/plugins/serve-index/serve-index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { KLAGE_DITTNAV_API } from '@app/config/config';
import { YTELSE_OVERVIEW_URL, isDeployed } from '@app/config/env';
import { indexFile } from '@app/index-file';
import { getLogger } from '@app/logger';
import { API_PROXY_PLUGIN_ID } from '@app/plugins/api-proxy';
import { viewCountCounter } from '@app/plugins/serve-index/counters';
import { getPaths } from '@app/plugins/serve-index/get-paths';
import type { RouteHandler } from 'fastify';
import { getAnonymousPaths, getLoggedInPaths } from '@app/plugins/serve-index/get-paths';
import { parsePath } from '@app/plugins/serve-index/parse-path';
import { Step } from '@app/plugins/serve-index/segments';
import { Type, type TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import type { FastifyReply, FastifyRequest } from 'fastify';
import fastifyPlugin from 'fastify-plugin';

const serveIndexHandler: RouteHandler = async (_, reply) => {
const log = getLogger('serve-index');

const serveIndexHandler = async (_: FastifyRequest, reply: FastifyReply) => {
viewCountCounter.inc();

reply.header('content-type', 'text/html');
Expand All @@ -14,12 +22,109 @@ const serveIndexHandler: RouteHandler = async (_, reply) => {
return reply.send(indexFile.indexFile);
};

const CREATE_OR_RESUME_PATH = isDeployed
? `http://${KLAGE_DITTNAV_API}/api/klanker`
: 'https://klage.dev.nav.no/api/klanker';

interface CreateOrResumeCase {
type: string;
innsendingsytelse: string;
internalSaksnummer?: string;
}

interface KlankeView {
id: string;
}

const isKlankeView = (value: unknown): value is KlankeView => {
if (typeof value !== 'object' || value === null) {
return false;
}

return 'id' in value;
};

class ApiError extends Error {
constructor(
message: string,
public readonly status: number,
) {
super(message);
}
}

const createOrResumeCase = async (oboToken: string, body: CreateOrResumeCase): Promise<KlankeView> => {
const res = await fetch(CREATE_OR_RESUME_PATH, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${oboToken}`,
},
body: JSON.stringify(body),
});

if (!res.ok) {
throw new ApiError(`Failed to create or resume case. Status: ${res.status}`, res.status);
}

const data = await res.json();

if (!isKlankeView(data)) {
throw new Error('Invalid response');
}

return data;
};

export const SERVE_INDEX_PLUGIN_ID = 'serve-index';

export const serveIndexPlugin = fastifyPlugin(
async (app) => {
for (const path of getPaths()) {
app.get(path, serveIndexHandler);
const typedApp = app.withTypeProvider<TypeBoxTypeProvider>();

for (const path of getLoggedInPaths()) {
typedApp.get(path, serveIndexHandler);
}

for (const path of getAnonymousPaths()) {
typedApp.get(
path,
{ schema: { querystring: Type.Object({ saksnummer: Type.Optional(Type.String()) }) } },
async (req, reply) => {
const oboToken = await req.ensureOboAccessToken(KLAGE_DITTNAV_API, reply);

if (oboToken === undefined) {
return serveIndexHandler(req, reply);
}

const parsed = parsePath(req.url);

if (parsed === null) {
return reply.redirect(YTELSE_OVERVIEW_URL, 302);
}

const { lang, type, innsendingsytelse } = parsed;
const internalSaksnummer = req.query.saksnummer;

try {
const caseData = await createOrResumeCase(oboToken, { type, innsendingsytelse, internalSaksnummer });

return reply.redirect(`/${lang}/sak/${caseData.id}/${Step.BEGRUNNELSE}`, 302);
} catch (error) {
log.error({ msg: 'Failed to create or resume case', error });

if (error instanceof ApiError) {
return reply.status(error.status).send(`Kunne ikke opprette eller gjenoppta sak. ${error.message}`);
}

if (error instanceof Error) {
return reply.status(500).send(`Kunne ikke opprette eller gjenoppta sak. ${error.message}`);
}

return reply.status(500).send('Kunne ikke opprette eller gjenoppta sak. Noe gikk galt.');
}
},
);
}
},
{ fastify: '4', name: SERVE_INDEX_PLUGIN_ID, dependencies: [API_PROXY_PLUGIN_ID] },
Expand Down

0 comments on commit 5655605

Please sign in to comment.