Skip to content

Commit

Permalink
Merge branch 'next' into task/OV-5-JWT-token
Browse files Browse the repository at this point in the history
  • Loading branch information
stefano-lacorazza authored Aug 27, 2024
2 parents e7c3c1c + df33fa5 commit c1b02a8
Show file tree
Hide file tree
Showing 30 changed files with 2,310 additions and 206 deletions.
10 changes: 10 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ DB_POOL_MAX=10
#
SECRET_KEY=IfcxMIXtiR3LC2CX0inOB3ozBuuTYqb7
EXPIRATION_TIME=24h

#
# AWS
#
AWS_ACCESS_KEY_ID=see-in-slack
AWS_SECRET_ACCESS_KEY=see-in-slack
AWS_S3_REGION=eu-north-1
AWS_S3_BUCKET_NAME=bsa-2024-outreachvids
AWS_CLOUDFRONT_DOMAIN_ID=d2tm5q3cg1nlwf

5 changes: 5 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"scripts": {
"lint:type": "npx tsc --noEmit",
"lint:js": "npx eslint \"src/**/*.ts\"",
"lint:js:fix": "npx eslint --fix \"src/**/*.ts\"",
"lint": "npm run lint:type && npm run lint:js",
"lint:fix": "npm run lint:type && npm run lint:js:fix",
"start:dev": "nodemon --exec tsx src/index.ts",
"migrate:dev": "node --loader ts-paths-esm-loader ../node_modules/knex/bin/cli.js migrate:latest",
"migrate:dev:make": "node --loader ts-paths-esm-loader ../node_modules/knex/bin/cli.js migrate:make -x ts",
Expand All @@ -27,6 +29,9 @@
"tsx": "4.17.0"
},
"dependencies": {
"@aws-sdk/client-s3": "3.635.0",
"@aws-sdk/s3-request-presigner": "3.635.0",
"@fastify/multipart": "8.3.0",
"@fastify/static": "7.0.4",
"@fastify/swagger": "8.15.0",
"@fastify/swagger-ui": "4.0.1",
Expand Down
35 changes: 35 additions & 0 deletions backend/src/common/config/base-config.package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,41 @@ class BaseConfig implements Config {
env: 'EXPIRATION_TIME',
default: null,
},
AWS: {
ACCESS_KEY_ID: {
doc: 'AWS access key id',
format: String,
env: 'AWS_ACCESS_KEY_ID',
default: null,
},
SECRET_ACCESS_KEY: {
doc: 'AWS secret access key',
format: String,
env: 'AWS_SECRET_ACCESS_KEY',
default: null,
},
S3: {
REGION: {
doc: 'AWS S3 region',
format: String,
env: 'AWS_S3_REGION',
default: null,
},
BUCKET_NAME: {
doc: 'AWS S3 bucket name',
format: String,
env: 'AWS_S3_BUCKET_NAME',
default: null,
},
},
CLOUDFRONT: {
DOMAIN_ID: {
doc: 'AWS CloudFront domain id',
format: String,
env: 'AWS_CLOUDFRONT_DOMAIN_ID',
default: null,
},
},
},
});
}
Expand Down
11 changes: 11 additions & 0 deletions backend/src/common/config/types/environment-schema.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ type EnvironmentSchema = {
SECRET_KEY: string;
EXPIRATION_TIME: string;
};
AWS: {
ACCESS_KEY_ID: string;
SECRET_ACCESS_KEY: string;
S3: {
REGION: string;
BUCKET_NAME: string;
};
CLOUDFRONT: {
DOMAIN_ID: string;
};
};
};

export { type EnvironmentSchema };
12 changes: 12 additions & 0 deletions backend/src/common/server-application/base-server-app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import fastifyMultipart from '@fastify/multipart';
import fastifyStatic from '@fastify/static';
import swagger, { type StaticDocumentSpec } from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
Expand Down Expand Up @@ -122,6 +123,15 @@ class BaseServerApp implements ServerApp {
);
}

private registerPlugins(): void {
this.app.register(fastifyMultipart, {
limits: {
fileSize: Number.POSITIVE_INFINITY,
files: 1,
},
});
}

private initValidationCompiler(): void {
this.app.setValidatorCompiler(
({ schema }: { schema: ValidationSchema }) => {
Expand Down Expand Up @@ -200,6 +210,8 @@ class BaseServerApp implements ServerApp {

await this.initMiddlewares();

this.registerPlugins();

this.initValidationCompiler();

this.initErrorHandler();
Expand Down
63 changes: 63 additions & 0 deletions backend/src/common/services/file/file.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

import { type BaseConfig } from '~/common/config/base-config.package.js';

class FileService {
private config: BaseConfig;
private client: S3Client;
private bucketName: string;
private cfDistributionId: string;

public constructor(config: BaseConfig) {
this.config = config;

this.client = this.initClient();
this.bucketName = this.config.ENV.AWS.S3.BUCKET_NAME;
this.cfDistributionId = this.config.ENV.AWS.CLOUDFRONT.DOMAIN_ID;
}

private initClient(): S3Client {
return new S3Client({
credentials: {
accessKeyId: this.config.ENV.AWS.ACCESS_KEY_ID,
secretAccessKey: this.config.ENV.AWS.SECRET_ACCESS_KEY,
},
region: this.config.ENV.AWS.S3.REGION,
});
}

public uploadFile = async (
buffer: Buffer,
fileName: string,
): Promise<void> => {
const command = new PutObjectCommand({
Bucket: this.bucketName,
Key: fileName,
Body: buffer,
});

await this.client.send(command);
};

public getFileUrl = async (fileName: string): Promise<string> => {
const getFileObject = new GetObjectCommand({
Bucket: this.bucketName,
Key: fileName,
});

return await getSignedUrl(this.client, getFileObject, {
expiresIn: 3600,
});
};

public getCloudFrontFileUrl = (fileName: string): string => {
return `https://${this.cfDistributionId}.cloudfront.net/${fileName}`;
};
}

export { FileService };
5 changes: 4 additions & 1 deletion backend/src/common/services/services.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { config } from '../config/config.js';
import { CryptService } from './crypt/crypt.service.js';
import { TokenService } from './token/token.services.js';
import { FileService } from './file/file.service.js';

const cryptService = new CryptService();
const fileService = new FileService(config);

const secretKey = config.ENV.TOKEN.SECRET_KEY;
const expirationTime = config.ENV.TOKEN.EXPIRATION_TIME;
const tokenService = new TokenService(secretKey, expirationTime);

export { cryptService, tokenService };
export { cryptService, fileService, tokenService };

4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"scripts": {
"lint:css": "npx stylelint \"src/**/*.scss\" --aei",
"lint:js": "npx eslint \"src/**/*.{ts,tsx}\"",
"lint:js:fix": "npx eslint --fix \"src/**/*.{ts,tsx}\"",
"lint:type": "npx tsc --noEmit",
"lint": "npm run lint:type && npm run lint:js",
"lint:fix": "npm run lint:type && npm run lint:js:fix",
"start:dev": "vite",
"build": "tsc -p tsconfig.build.json && vite build",
"preview": "vite preview"
Expand All @@ -34,12 +36,14 @@
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@reduxjs/toolkit": "2.2.7",
"@remotion/player": "4.0.201",
"formik": "2.4.6",
"framer-motion": "11.3.24",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-redux": "9.1.2",
"react-router-dom": "6.26.0",
"remotion": "4.0.201",
"shared": "*",
"zod-formik-adapter": "1.3.0"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { type UserSignUpRequestDto } from '~/bundles/users/users.js';

const DEFAULT_SIGN_UP_PAYLOAD: UserSignUpRequestDto = {
name: '',
email: '',
password: '',
confirmPassword: '',
};

export { DEFAULT_SIGN_UP_PAYLOAD };
69 changes: 58 additions & 11 deletions frontend/src/bundles/auth/components/sign-up-form/sign-up-form.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,99 @@
import { UserValidationMessage } from 'shared/src/bundles/users/users.js';

import {
Box,
Button,
FormProvider,
Heading,
Input,
Link,
VStack,
} from '~/bundles/common/components/components.js';
import { useAppForm } from '~/bundles/common/hooks/hooks.js';
import { AppRoute, DataStatus } from '~/bundles/common/enums/enums.js';
import {
useAppForm,
useAppSelector,
useMemo,
} from '~/bundles/common/hooks/hooks.js';
import {
type UserSignUpRequestDto,
userSignUpValidationSchema,
} from '~/bundles/users/users.js';

import { FormError, FormHeader, PasswordInput } from '../common/components.js';
import { DEFAULT_SIGN_UP_PAYLOAD } from './constants/constants.js';

type Properties = {
onSubmit: (payload: UserSignUpRequestDto) => void;
};

const SignUpForm: React.FC<Properties> = ({ onSubmit }) => {
const { dataStatus } = useAppSelector(({ auth }) => ({
dataStatus: auth.dataStatus,
}));
const form = useAppForm<UserSignUpRequestDto>({
initialValues: DEFAULT_SIGN_UP_PAYLOAD,
validationSchema: userSignUpValidationSchema,
onSubmit,
});

const { handleSubmit } = form;
const { handleSubmit, errors, values } = form;

const isEmpty = useMemo(
() => Object.values(values).some((value) => value.trim().length === 0),
[values],
);

return (
<FormProvider value={form}>
<Box bg="brand.200" w={64} p={6} rounded="md">
<Heading as="h1">Sign Up</Heading>

<Box w="55%" color="white">
<FormHeader
headerText="Create an account"
subheader={
<>
Already registerd?{' '}
<Link to={AppRoute.SIGN_IN} variant="secondary">
Log In
</Link>
</>
}
/>
<form onSubmit={handleSubmit}>
<VStack spacing={4} align="flex-start">
<Input
type="text"
label="Full Name"
placeholder="Name"
name="name"
/>
<Input
type="email"
label="Email"
placeholder="Enter your email"
placeholder="[email protected]"
name="email"
/>
<Input
type="password"
<PasswordInput
label="Password"
placeholder="Enter your password"
name="password"
hasError={Boolean(errors.password)}
/>
<PasswordInput
label="Repeat password"
name="confirmPassword"
hasError={Boolean(errors.confirmPassword)}
/>
<FormError
isVisible={dataStatus === DataStatus.REJECTED}
message={
UserValidationMessage.USER_IS_NOT_AVAILABLE
}
/>
<Button
type="submit"
label="Sign up"
size="lg"
sx={{ mt: '16px' }}
isDisabled={isEmpty}
/>
<Button type="submit" label="Sign up" />
</VStack>
</form>
</Box>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/bundles/auth/store/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ const { reducer, actions, name } = createSlice({
builder.addCase(signUp.pending, (state) => {
state.dataStatus = DataStatus.PENDING;
});
builder.addCase(signUp.fulfilled, (state) => {
builder.addCase(signUp.fulfilled, (state, action) => {
const payload = action.payload;

state.dataStatus = DataStatus.FULFILLED;
state.user = payload;
});
builder.addCase(signUp.rejected, (state) => {
state.dataStatus = DataStatus.REJECTED;
state.user = null;
});
},
});
Expand Down
1 change: 1 addition & 0 deletions frontend/src/bundles/common/components/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { Input } from './input/input.js';
export { Link } from './link/link.js';
export { Loader } from './loader/loader.js';
export { Overlay } from './overlay/overlay.js';
export { Player } from './player/player.js';
export { RouterProvider } from './router-provider/router-provider.js';
export { VideoModal } from './video-modal/video-modal.js';
export { DownloadIcon } from '@chakra-ui/icons';
Expand Down
Loading

0 comments on commit c1b02a8

Please sign in to comment.