Skip to content

Commit

Permalink
Merge pull request #52 from jembi/fix/docker-image-build-for-production
Browse files Browse the repository at this point in the history
Fix/docker image build for production
  • Loading branch information
BMartinos authored Sep 12, 2024
2 parents 3329da8 + a4013d6 commit 653b873
Show file tree
Hide file tree
Showing 36 changed files with 556 additions and 445 deletions.
21 changes: 14 additions & 7 deletions .env
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
POSTGRES_DB=replaceme
POSTGRES_USER=replaceme
POSTGRES_PASSWORD=replaceme
KEYCLOAK_ADMIN_PASSWORD=replaceme
KEYCLOAK_ADMIN=replaceme
EXTERNAL_URL=replaceme
PRISMA_FIELD_ENCRYPTION_KEY=k1.aesgcm256.DbQoar8ZLuUsOHZNyrnjlskInHDYlzF3q6y1KGM7DUN=
PRISMA_FIELD_ENCRYPTION_HASH_SALT=replaceme
PRISMA_FIELD_ENCRYPTION_HASH_SALT=0be97e77063ea3f7a0f128b06ef9b1eg
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
EXTERNAL_URL=http://localhost:3001
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=password
KEYCLOAK_CLIENT_ID="nextjs"
KEYCLOAK_CLIENT_SECRET="gX0nfIp57bO3bY68br3kdTXung2Auwpr"
KEYCLOAK_ISSUER="http://localhost:8082/realms/shlp"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="ArmMS4LBmH3VTR77UrSAY2lPs04HO0Nk2/4BcU0jMvc="
NEXT_CONTAINER_KEYCLOAK_ENDPOINT="http://localhost:8082"
NEXT_LOCAL_KEYCLOAK_URL="http://localhost:8082"
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- ./public:/app/public
- /app/.next
ports:
- "3000:3000"
- "3080:3000"
networks:
- smart-health-links-portal-network
healthcheck:
Expand Down Expand Up @@ -50,7 +50,7 @@ services:
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- 8080:8080
- 8082:8080
volumes:
- ./import/config/shlp:/opt/keycloak/data/import
restart: always
Expand Down
29 changes: 3 additions & 26 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import NextAuth, { AuthOptions } from "next-auth";
import KeycloakProvider from "next-auth/providers/keycloak";
import NextAuth from 'next-auth';

import { authOptions } from '../authOptions';

export const authOptions: AuthOptions = {
providers: [
KeycloakProvider({
jwks_endpoint: `${process.env.NEXT_CONTAINER_KEYCLOAK_ENDPOINT}/realms/shlp/protocol/openid-connect/certs`,
wellKnown: undefined,
clientId: process.env.KEYCLOAK_CLIENT_ID,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
issuer: process.env.KEYCLOAK_ISSUER,
authorization: {
params: {
scope: "openid email profile",
},
url: `${process.env.NEXT_LOCAL_KEYCLOAK_URL}/realms/shlp/protocol/openid-connect/auth`,
},
token: `${process.env.NEXT_CONTAINER_KEYCLOAK_ENDPOINT}/realms/shlp/protocol/openid-connect/token`,
userinfo: `${process.env.NEXT_CONTAINER_KEYCLOAK_ENDPOINT}/realms/shlp/protocol/openid-connect/userinfo`,
}),
],
callbacks: {
session: async({session, token, user}) => {
return {...session, token, user};
}
}
};
const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
5 changes: 5 additions & 0 deletions src/app/api/auth/authOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ export const authOptions: AuthOptions = {
userinfo: `${process.env.NEXT_CONTAINER_KEYCLOAK_ENDPOINT}/realms/shlp/protocol/openid-connect/userinfo`,
}),
],
callbacks: {
session: async ({ session, token, user }) => {
return { ...session, token, user };
},
},
};
6 changes: 3 additions & 3 deletions src/app/api/docs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getApiDocs } from "@/lib/swagger";
import { getApiDocs } from '@/lib/swagger';

import ReactSwagger from "./react-swagger";
import ReactSwagger from './react-swagger';

export default async function IndexPage() {
const spec = await getApiDocs();
Expand All @@ -9,4 +9,4 @@ export default async function IndexPage() {
<ReactSwagger spec={spec} />
</section>
);
}
}
4 changes: 2 additions & 2 deletions src/app/api/docs/react-swagger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';

type Props = {
spec: Record<string, any>,
spec: Record<string, any>;
};

function ReactSwagger({ spec }: Props) {
return <SwaggerUI spec={spec} />;
}

export default ReactSwagger;
export default ReactSwagger;
64 changes: 32 additions & 32 deletions src/app/api/v1/share-links/[id]/accesses/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,39 @@
* @jest-environment node
*/

import { NOT_FOUND } from "@/app/constants/http-constants";
import { handleApiValidationError } from "@/app/utils/error-handler";
import { mapModelToDto } from "@/mappers/shlink-access-mapper";
import { getSHLinkAccessesUseCase } from "@/usecases/shlink-access/get-shlink-accesses";
import { getSingleSHLinkUseCase } from "@/usecases/shlinks/get-single-shlink";
import { NOT_FOUND } from '@/app/constants/http-constants';
import { handleApiValidationError } from '@/app/utils/error-handler';
import { mapModelToDto } from '@/mappers/shlink-access-mapper';
import { getSHLinkAccessesUseCase } from '@/usecases/shlink-access/get-shlink-accesses';
import { getSingleSHLinkUseCase } from '@/usecases/shlinks/get-single-shlink';

import { POST } from "./route";
import { POST } from './route';

jest.mock("@/container", () => ({
jest.mock('@/container', () => ({
container: {
get: jest.fn(),
},
SHLinkAccessRepositoryToken: Symbol('SHLinkAccessRepositoryToken'),
SHLinkRepositoryToken: Symbol('SHLinkRepositoryToken'),
}));

jest.mock("@/usecases/shlink-access/get-shlink-accesses", () => ({
jest.mock('@/usecases/shlink-access/get-shlink-accesses', () => ({
getSHLinkAccessesUseCase: jest.fn(),
}));

jest.mock("@/usecases/shlinks/get-single-shlink", () => ({
jest.mock('@/usecases/shlinks/get-single-shlink', () => ({
getSingleSHLinkUseCase: jest.fn(),
}));

jest.mock("@/mappers/shlink-access-mapper", () => ({
jest.mock('@/mappers/shlink-access-mapper', () => ({
mapModelToDto: jest.fn(),
}));

jest.mock("@/app/utils/error-handler", () => ({
jest.mock('@/app/utils/error-handler', () => ({
handleApiValidationError: jest.fn(),
}));

describe("POST function", () => {
describe('POST function', () => {
let mockGetSingleSHLinkUseCase: jest.Mock;
let mockGetSHLinkAccessesUseCase: jest.Mock;
let mockMapModelToDto: jest.Mock;
Expand All @@ -47,13 +47,13 @@ describe("POST function", () => {
mockHandleApiValidationError = handleApiValidationError as jest.Mock;
});

it("should return 404 if shlink is not found", async () => {
it('should return 404 if shlink is not found', async () => {
// Arrange
const request = new Request("http://localhost", {
method: "POST",
body: JSON.stringify({ managementToken: "token" }),
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ managementToken: 'token' }),
});
const params = { id: "non-existent-id" };
const params = { id: 'non-existent-id' };

mockGetSingleSHLinkUseCase.mockResolvedValue(null);

Expand All @@ -65,16 +65,16 @@ describe("POST function", () => {
expect(await response.json()).toEqual({ message: NOT_FOUND });
});

it("should return 200 with access data if shlink is found", async () => {
it('should return 200 with access data if shlink is found', async () => {
// Arrange
const request = new Request("http://localhost", {
method: "POST",
body: JSON.stringify({ managementToken: "token" }),
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ managementToken: 'token' }),
});
const params = { id: "existent-id" };
const shlink = { getId: jest.fn().mockReturnValue("shlink-id") };
const accesses = [{ id: "access-id-1" }, { id: "access-id-2" }];
const dto = { id: "access-id-1" };
const params = { id: 'existent-id' };
const shlink = { getId: jest.fn().mockReturnValue('shlink-id') };
const accesses = [{ id: 'access-id-1' }, { id: 'access-id-2' }];
const dto = { id: 'access-id-1' };

mockGetSingleSHLinkUseCase.mockResolvedValue(shlink);
mockGetSHLinkAccessesUseCase.mockResolvedValue(accesses);
Expand All @@ -85,18 +85,18 @@ describe("POST function", () => {

// Assert
expect(response.status).toBe(200);
expect(await response.json()).toEqual(accesses.map(x => ({ id: x.id })));
expect(await response.json()).toEqual(accesses.map((x) => ({ id: x.id })));
});

it("should handle errors and call handleApiValidationError", async () => {
it('should handle errors and call handleApiValidationError', async () => {
// Arrange
const request = new Request("http://localhost", {
method: "POST",
body: JSON.stringify({ managementToken: "token" }),
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ managementToken: 'token' }),
});
const params = { id: "some-id" };
const params = { id: 'some-id' };

mockGetSingleSHLinkUseCase.mockRejectedValue(new Error("Test error"));
mockGetSingleSHLinkUseCase.mockRejectedValue(new Error('Test error'));

// Act
await POST(request, params);
Expand Down
64 changes: 39 additions & 25 deletions src/app/api/v1/share-links/[id]/accesses/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { NextResponse } from "next/server";
import { NextResponse } from 'next/server';

import { NOT_FOUND } from "@/app/constants/http-constants";
import { handleApiValidationError } from "@/app/utils/error-handler";
import { container, SHLinkAccessRepositoryToken, SHLinkRepositoryToken } from "@/container";
import { SHLinkAccessRequestDto } from "@/domain/dtos/shlink-access";
import { ISHLinkAccessRepository } from "@/infrastructure/repositories/interfaces/shlink-access-repository";
import { ISHLinkRepository } from "@/infrastructure/repositories/interfaces/shlink-repository";
import { mapModelToDto } from "@/mappers/shlink-access-mapper";
import { getSHLinkAccessesUseCase } from "@/usecases/shlink-access/get-shlink-accesses";
import { getSingleSHLinkUseCase } from "@/usecases/shlinks/get-single-shlink";
import { NOT_FOUND } from '@/app/constants/http-constants';
import { handleApiValidationError } from '@/app/utils/error-handler';
import {
container,
SHLinkAccessRepositoryToken,
SHLinkRepositoryToken,
} from '@/container';
import { SHLinkAccessRequestDto } from '@/domain/dtos/shlink-access';
import { ISHLinkAccessRepository } from '@/infrastructure/repositories/interfaces/shlink-access-repository';
import { ISHLinkRepository } from '@/infrastructure/repositories/interfaces/shlink-repository';
import { mapModelToDto } from '@/mappers/shlink-access-mapper';
import { getSHLinkAccessesUseCase } from '@/usecases/shlink-access/get-shlink-accesses';
import { getSingleSHLinkUseCase } from '@/usecases/shlinks/get-single-shlink';

const repo = container.get<ISHLinkAccessRepository>(SHLinkAccessRepositoryToken);
const repo = container.get<ISHLinkAccessRepository>(
SHLinkAccessRepositoryToken,
);
const shlinkRepo = container.get<ISHLinkRepository>(SHLinkRepositoryToken);

/**
Expand Down Expand Up @@ -40,18 +46,26 @@ const shlinkRepo = container.get<ISHLinkRepository>(SHLinkRepositoryToken);
* items:
* $ref: '#/components/schemas/SHLinkAccess'
*/
export async function POST(request: Request, params: {id: string }) {
try{
const dto: SHLinkAccessRequestDto = await request.json();
const shlink = await getSingleSHLinkUseCase({ repo: shlinkRepo }, { id: params.id, managementToken: dto.managementToken });
if(!shlink){
return NextResponse.json({ message: NOT_FOUND }, { status: 404 });
}

const accesses = await getSHLinkAccessesUseCase({ repo }, { shlinkId: shlink.getId() });
return NextResponse.json(accesses.map(x => mapModelToDto(x)), { status: 200 });
export async function POST(request: Request, params: { id: string }) {
try {
const dto: SHLinkAccessRequestDto = await request.json();
const shlink = await getSingleSHLinkUseCase(
{ repo: shlinkRepo },
{ id: params.id, managementToken: dto.managementToken },
);
if (!shlink) {
return NextResponse.json({ message: NOT_FOUND }, { status: 404 });
}
catch(error){
return handleApiValidationError(error);
}
}

const accesses = await getSHLinkAccessesUseCase(
{ repo },
{ shlinkId: shlink.getId() },
);
return NextResponse.json(
accesses.map((x) => mapModelToDto(x)),
{ status: 200 },
);
} catch (error) {
return handleApiValidationError(error);
}
}
8 changes: 6 additions & 2 deletions src/app/api/v1/share-links/[id]/deactivate/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,16 @@ describe('DELETE /api/v1/share-link/[id]/deactivate', () => {
});

it('should return deactivated link DTO and status 200 when link is found', async () => {
(getUserProfile as jest.Mock).mockResolvedValue({id: 'user-123456', name: '', email: ''});
(getUserProfile as jest.Mock).mockResolvedValue({
id: 'user-123456',
name: '',
email: '',
});
(deactivateSHLinksUseCase as jest.Mock).mockResolvedValue(mockModel);
(mapModelToDto as jest.Mock).mockReturnValue(mockDto);

const request = mockRequest();
const response = await DELETE(request, {params: {id: 'user-123456',}});
const response = await DELETE(request, { params: { id: 'user-123456' } });

expect(response).toBeInstanceOf(NextResponse);
expect(response.status).toBe(200);
Expand Down
41 changes: 23 additions & 18 deletions src/app/api/v1/share-links/[id]/deactivate/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { NextResponse } from "next/server";
import { NextResponse } from 'next/server';

import { NOT_FOUND } from "@/app/constants/http-constants";
import { getUserProfile } from "@/app/utils/authentication";
import { handleApiValidationError } from "@/app/utils/error-handler";
import { container, SHLinkRepositoryToken } from "@/container";
import { ISHLinkRepository } from "@/infrastructure/repositories/interfaces/shlink-repository";
import { mapModelToDto } from "@/mappers/shlink-mapper";
import { deactivateSHLinksUseCase } from "@/usecases/shlinks/deactivate-shlink";
import { NOT_FOUND } from '@/app/constants/http-constants';
import { getUserProfile } from '@/app/utils/authentication';
import { handleApiValidationError } from '@/app/utils/error-handler';
import { container, SHLinkRepositoryToken } from '@/container';
import { ISHLinkRepository } from '@/infrastructure/repositories/interfaces/shlink-repository';
import { mapModelToDto } from '@/mappers/shlink-mapper';
import { deactivateSHLinksUseCase } from '@/usecases/shlinks/deactivate-shlink';

const repo = container.get<ISHLinkRepository>(SHLinkRepositoryToken);

Expand All @@ -30,15 +30,20 @@ const repo = container.get<ISHLinkRepository>(SHLinkRepositoryToken);
* type: object
* $ref: '#/components/schemas/SHLinkMini'
*/
export async function DELETE(request: Request, { params }: { params: { id: string } }) {
try{
const user = await getUserProfile(request);
const result = await deactivateSHLinksUseCase({ repo }, { id: params.id, user });
const data = mapModelToDto(result);
if(result) return NextResponse.json(data, { status: 200 });
return NextResponse.json({ message: NOT_FOUND }, { status: 404});
}
catch(error){
return handleApiValidationError(error);
export async function DELETE(
request: Request,
{ params }: { params: { id: string } },
) {
try {
const user = await getUserProfile(request);
const result = await deactivateSHLinksUseCase(
{ repo },
{ id: params.id, user },
);
const data = mapModelToDto(result);
if (result) return NextResponse.json(data, { status: 200 });
return NextResponse.json({ message: NOT_FOUND }, { status: 404 });
} catch (error) {
return handleApiValidationError(error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export async function GET(
{ id: params.id },
);

if(!shlink) return NextResponse.json({ message: NOT_FOUND }, { status: 404 });
if (!shlink)
return NextResponse.json({ message: NOT_FOUND }, { status: 404 });

const user = await getUserUseCase(
{ repo: userRepo },
Expand Down
Loading

0 comments on commit 653b873

Please sign in to comment.