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

Fix/docker image build for production #52

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .env
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
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
KEYCLOAK_ADMIN_PASSWORD=admin
BMartinos marked this conversation as resolved.
Show resolved Hide resolved
KEYCLOAK_ADMIN=admin
EXTERNAL_URL=http://localhost:3001
KEYCLOAK_ADMIN_PASSWORD=password
KEYCLOAK_CLIENT_ID="nextjs"
BMartinos marked this conversation as resolved.
Show resolved Hide resolved
KEYCLOAK_CLIENT_SECRET="gX0nfIp57bO3bY68br3kdTXung2Auwpr"
KEYCLOAK_ISSUER="http://localhost:8082/realms/shlp"
BMartinos marked this conversation as resolved.
Show resolved Hide resolved
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="ArmMS4LBmH3VTR77UrSAY2lPs04HO0Nk2/4BcU0jMvc="
NEXT_CONTAINER_KEYCLOAK_ENDPOINT="http://localhost:8082"
BMartinos marked this conversation as resolved.
Show resolved Hide resolved
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
Loading