Skip to content

Commit

Permalink
OK-436 refaktoroitu datafetchit, hanskattu keskitetysti autentikointi…
Browse files Browse the repository at this point in the history
… ja lisätty retry ym
  • Loading branch information
marjakari committed Sep 17, 2024
1 parent 4379a0d commit 6d75802
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const LahettavaPalveluInput = ({
const doFetchLahettavatPalvelut = async (): Promise<string[]> => {
console.info('doFetchLahettavatPalvelut');
const response = await fetchLahettavatPalvelut();
console.info(response)
return response;
};

Expand Down
2 changes: 2 additions & 0 deletions viestinvalitys-raportointi/src/app/i18n/locales/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
"error": {
"404.otsikko": "Sivua ei löytynyt",
"404.teksti": "Etsittyä sivua ei löytynyt.",
"access-denied": "Ei riittäviä käyttöoikeuksia",
"fetch": "Tietojen haussa tapahtui virhe",
"otsikko": "Tapahtui virhe",
"teksti": "Palvelussa tapahtui virhe."
},
Expand Down
162 changes: 19 additions & 143 deletions viestinvalitys-raportointi/src/app/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ import { cookies } from 'next/headers';
import { LahetysHakuParams, OrganisaatioSearchResult, VastaanottajatHakuParams } from './types';
import { apiUrl, cookieName, loginUrl, virkailijaUrl } from './configurations';
import { redirect } from 'next/navigation';
import { makeRequest } from './http-client';

const LAHETYKSET_SIVUTUS_KOKO = 20;
const VASTAANOTTAJAT_SIVUTUS_KOKO = 10;
const REVALIDATE_TIME_SECONDS = 60 * 60 * 2;
const REVALIDATE_ASIOINTIKIELI = 60;

// TODO apuwrapperi headerien asettamiseen ja virheenkäsittelyyn
export async function fetchLahetykset(hakuParams: LahetysHakuParams) {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const fetchUrlBase = `${apiUrl}/lahetykset/lista?enintaan=${LAHETYKSET_SIVUTUS_KOKO}`;
// eslint-disable-next-line no-var
var fetchParams = hakuParams.seuraavatAlkaen
Expand All @@ -30,53 +25,24 @@ export async function fetchLahetykset(hakuParams: LahetysHakuParams) {
if(hakuParams?.palvelu) {
fetchParams += `&palvelu=${hakuParams.palvelu}`;
}
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(fetchUrlBase.concat(fetchParams), {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(fetchUrlBase.concat(fetchParams), {
cache: 'no-store',
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
console.info('http 401, redirect to login');
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error(res.statusText);
}
return res.json();
return res.data;
}

export async function fetchLahetys(lahetysTunnus: string) {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/lahetykset/${lahetysTunnus}`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url, {
cache: 'no-store',
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error(res.statusText);
}
return res.json();
return res.data;
}

export async function fetchLahetyksenVastaanottajat(
lahetysTunnus: string,
hakuParams: VastaanottajatHakuParams,
) {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/lahetykset/${lahetysTunnus}/vastaanottajat?enintaan=${VASTAANOTTAJAT_SIVUTUS_KOKO}`;
// eslint-disable-next-line no-var
var fetchParams = hakuParams.alkaen
Expand All @@ -91,90 +57,38 @@ export async function fetchLahetyksenVastaanottajat(
if (hakuParams?.organisaatio) {
fetchParams += `&organisaatio=${hakuParams.organisaatio}`;
}
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url.concat(fetchParams), {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url.concat(fetchParams), {
cache: 'no-store',
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error(res.statusText);
}
return res.json();
return res.data;
}

export async function fetchMassaviesti(lahetysTunnus: string) {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/massaviesti/${lahetysTunnus}`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url, {
cache: 'no-store',
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error(res.statusText);
}
return res.json();
return res.data;
}

export async function fetchViesti(viestiTunnus: string) {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/viesti/${viestiTunnus}`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url, {
cache: 'no-store',
});
console.info(res.status);
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error(res.statusText);
}
return res.json();
return res.data;
}

export async function fetchAsiointikieli() {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
console.info(loginUrl);
redirect(loginUrl);
}
const url = `${apiUrl}/omattiedot`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url, {
next: { revalidate: REVALIDATE_ASIOINTIKIELI }
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error('asiointikielen haku epäonnistui');
}
return res.json();
return res.data;
}

export async function fetchLokalisaatiot(lang: string) {
// ei tarvi käyttää http-clientia kun ei vaadi tunnistautumista ja on tiedostot fallbackina
const url = `${virkailijaUrl}/lokalisointi/cxf/rest/v1/localisation?category=viestinvalitys&locale=`;
const res = await fetch(`${url}${lang}`, {
next: { revalidate: REVALIDATE_TIME_SECONDS },
Expand All @@ -183,34 +97,15 @@ export async function fetchLokalisaatiot(lang: string) {
}

export async function fetchOrganisaatioRajoitukset() {
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/organisaatiot/oikeudet`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
cache: 'no-store', // caching in backend
const res = await makeRequest(url, {
cache: 'no-store',
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error('organisaatio-oikeuksien haku epäonnistui');
}
return res.json();
return res.data;
}

export async function searchOrganisaatio(searchStr: string): Promise<OrganisaatioSearchResult> {
console.info('haetaan organisaatiota');
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
// organisaatiorajaus oikeuksien mukaan
const oidRestrictionList: string[] = await fetchOrganisaatioRajoitukset();
const oidRestrictionParams =
Expand All @@ -224,33 +119,14 @@ export async function searchOrganisaatio(searchStr: string): Promise<Organisaati
csrf: '1.2.246.562.10.00000000001.viestinvalityspalvelu',
},
});
if (!(res.ok || res.status === 400 || res.status === 410)) {
// This will activate the closest `error.js` Error Boundary
throw new Error('organisaation haku epäonnistui');
}
return res.json();
}

export async function fetchLahettavatPalvelut(): Promise<string[]> {
console.info('haetaan lähettävät palvelut');
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
console.info('no session cookie, redirect to login');
redirect(loginUrl);
}
const url = `${apiUrl}/palvelut`;
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const res = await fetch(url, {
headers: { cookie: cookieParam ?? '' }, // Forward the authorization header
const res = await makeRequest(url, {
cache: 'no-store',
});
console.info(res.status)
if (!(res.ok || res.status === 400 || res.status === 410)) {
if (res.status === 401) {
redirect(loginUrl);
}
// This will activate the closest `error.js` Error Boundary
throw new Error('lähettävien palvelujen haku epäonnistui');
}
return res.json();
return res.data;
}
16 changes: 16 additions & 0 deletions viestinvalitys-raportointi/src/app/lib/error-handling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class FetchError extends Error {
response: Response;
constructor(response: Response, message: string = 'error.fetch') {
super(message);
// Set the prototype explicitly.
Object.setPrototypeOf(this, FetchError.prototype);
this.response = response;
}
}

export class PermissionError extends Error {
constructor(message: string = 'error.access-denied') {
super(message);
}
}

101 changes: 101 additions & 0 deletions viestinvalitys-raportointi/src/app/lib/http-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { redirect } from 'next/navigation';
import { apiUrl, cookieName, loginUrl } from './configurations';
import { FetchError } from "./error-handling";
import { cookies } from 'next/headers';

/* sovellettu valintojen-totettaminen repon vastaavasta */
const doFetch = async (request: Request) => {
try {
const response = await fetch(request);
return response.status >= 400
? Promise.reject(new FetchError(response))
: Promise.resolve(response);
} catch (e) {
return Promise.reject(e);
}
};

const isUnauthenticated = (response: Response) => {
return response?.status === 401;
};

const isRedirected = (response: Response) => {
return response.redirected;
};

const makeBareRequest = (request: Request) => {
request.headers.set('Caller-Id', '1.2.246.562.10.00000000001.viestinvalityspalvelu');
request.headers.set('CSRF', '1.2.246.562.10.00000000001.viestinvalityspalvelu')
return doFetch(request);
};

const retryWithLogin = async (request: Request, loginUrl: string) => {
await makeBareRequest(new Request(loginUrl));
return makeBareRequest(request);
};

const responseToData = async (res: Response) => {
if (res.status === 204) {
return { data: {} };
}
try {
const result = { data: await res.json() }; // toistaiseksi kaikki kutsuttavat apit palauttavat jsonia
return result;
} catch (e) {
console.error('Parsing fetch response body as JSON failed!');
return Promise.reject(e);
}
};

export const makeRequest = async (url: string, options: RequestInit = {}) => {
console.info('Tehdään request urliin ' + url)
// autentikointicookie
const sessionCookie = cookies().get(cookieName);
if (sessionCookie === undefined) {
redirect(loginUrl);
}
const cookieParam = sessionCookie.name + '=' + sessionCookie.value;
const request = new Request(url, { method: 'GET', headers: { cookie: cookieParam ?? '' }, ...options })
const originalRequest = request.clone();
try {
const response = await makeBareRequest(request);
const responseUrl = new URL(response.url);
if (
isRedirected(response) &&
responseUrl.pathname.startsWith('/cas/login')
) {
redirect(loginUrl);
}
return responseToData(response);
} catch (error: unknown) {
console.error('Virhe tietojen haussa')
if (error instanceof FetchError) {
console.error(error.response.status)
console.error(error.message)
if (isUnauthenticated(error.response)) {
try {
if (request?.url?.includes(apiUrl)) {
const resp = await retryWithLogin(request, loginUrl);
return responseToData(resp);
}
} catch (e) {
if (e instanceof FetchError && isUnauthenticated(e.response)) {
redirect(loginUrl);
}
return Promise.reject(e);
}
} else if (
isRedirected(error.response) &&
error.response.url === request.url
) {
//Some backend services lose the original method and headers, so we need to do retry with cloned request
const response = await makeBareRequest(originalRequest);
return responseToData(response);
}
}
return Promise.reject(error);
}
};



0 comments on commit 6d75802

Please sign in to comment.