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

Set TypeScript to strict mode and fix issues related to server types #261

Merged
merged 7 commits into from
Jun 25, 2022

Conversation

jbaez
Copy link
Contributor

@jbaez jbaez commented Jun 24, 2022

Added new npm commands to run lint and check code. We would normally just use npm run check:all which does all the checks.

Fixed all the lint and TypeScript issues, removing unused imports and variables, adding types and interfaces, and updating the code where needed.

The commits in this PR have more info on the changes made

- set TypeScript in strict mode
- add npm commands to lint / check code
- fix all lint issues
- fix some TS issues
- rename User reponse DTO to make it consistent with the other ones
- override Express/User interface to use UserResponseDto interface
 This is for when the accessing the `user` from a Express Request,
 like in `asset-upload-config`
- fix all the remaining TypeScript errors
- add missing `@types/mapbox__mapbox-sdk` package
@jbaez jbaez requested a review from alextran1502 June 24, 2022 22:46
Copy link
Contributor

@alextran1502 alextran1502 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for another high-quality PR to improve the code base! I've left some comments and an idea to fix some issues while running on the dev server. Let me know what do you think

@@ -3,32 +3,32 @@ import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeor
@Entity('users')
export class UserEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
id!: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume ! further indicating that this property must exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly, it's called definite assignment assertion. In general we should only be using these in Entities where properties are not null, because TypeORM cannot use a constructor to initialise the values. So we know that after the Entity is "hydrated" by TypeORM, those properties would have its value.

@@ -39,7 +39,7 @@ export class AlbumRepository implements IAlbumRepository {
) {}

async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> {
return await getConnection().transaction(async (transactionalEntityManager) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does strict mode suggest getting rid of await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, I just removed it because in these case is not useful. When using await what it does is, it resolves the promise, so we now have the "album entity", then it wraps it in another resolved promise which is then returned.

Without the await it just returns the promise. A good practice is to have the return values defined in the function (don't let TypeScript infer), in this case Promise<AlbumEntity>, so whatever you do inside you must still return that type or TypeScript will complain.

@@ -25,6 +25,7 @@ describe('Album service', () => {
albumEntity.createdAt = 'date';
albumEntity.sharedUsers = [];
albumEntity.assets = [];
albumEntity.albumThumbnailAssetId = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I wonder prior to this change, I was still able to get the photo thumbnail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entity has defined albumThumbnailAssetId as nullable: true. So when TypeORM "hydrated" the entity, it would set it to null or string. This spec was not setting albumThumbnailAssetId to anything (undefined), so the type was not matching what TypeORM would "hydrate" it to, and TypeScript was complaining because I used undefined (instead of null) later in one of the tests for checking the expectation.

@@ -166,6 +194,10 @@ export class AssetService {
res.set({
'Content-Type': 'image/jpeg',
});
if (!asset.resizePath) {
Logger.error('Error serving IMAGE asset for web ');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be Logger.error('Error serving IMAGE asset for web', 'ServeFile'); and the line below will be throw new InternalServerErrorException(Failed to serve image asset for web);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I based those lines in the ones below in line 198-199:

Logger.error('Error serving IMAGE asset ', e);
throw new InternalServerErrorException(`Failed to serve image asset ${e}`, 'ServeFile');

Looks like all InternalServerErrorException in this file use ServeFile as the second param. So I think it is consistent with the other ones.

Agree, we could also add ServeFile as the second argument of Logger.error, since we don't have an error to pass as in there (like in the other places).

Let me know what you think

@@ -44,10 +55,14 @@ export class AssetService {
asset.modifiedAt = assetInfo.modifiedAt;
asset.isFavorite = assetInfo.isFavorite;
asset.mimeType = mimeType;
asset.duration = assetInfo.duration;
asset.duration = assetInfo.duration || null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from the mobile app, if the asset is an image, it still return the duration and it will be "0:00:00.00000" so I think this line will be

asset.duration = assetInfo.duration || '0:00:00.00000';

So we need to fix the duration type as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, then the best place to fix it would be in the AssetResponseDto. So this line would correct, because that's just how it's saved in the DB. And I'll update the AssetResponseDto so duration is of type string, and when we map the entity to the DTO, if duration is null, it would return 0:00:00.00000

const yearInfo = new Date(fileInfo.createdAt).getFullYear();
const monthInfo = new Date(fileInfo.createdAt).getMonth();
// const yearInfo = new Date(fileInfo.createdAt).getFullYear();
// const monthInfo = new Date(fileInfo.createdAt).getMonth();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would it let sit here as a reminder :P

console.log('error deleting ', asset.originalPath);
}
});
// TODO: what if there is no asset.resizePath. Should fail the Job?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no asset.resizePath then it should try and then log out like below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your current implementation work!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I put the if, when asset.resizePath was undefined it would probably throw an error. Since I made it safe adding the if, it would not longer throw an error.

I imagine the Job might fail if there is an unhandled error thrown 🤔. An error from the callback of fs.unlink doesn't seem like it would make the Job fail either. Maybe it should use something like job.moveToFailed(), or maybe just ignoring it it's fine in this case 😄

@@ -2,5 +2,10 @@ import { Controller, Get, Res, Headers } from '@nestjs/common';
import { Response } from 'express';
@Controller()
export class AppController {
constructor() {}
@Get()
async redirectToWebpage(@Res({ passthrough: true }) res: Response, @Headers() headers: Record<string, string>) {
Copy link
Contributor

@alextran1502 alextran1502 Jun 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed on the main branch.

Copy link
Contributor Author

@jbaez jbaez Jun 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, sorry my bad. I had a merge conflict which I solved the wrong way.

@@ -128,7 +127,7 @@ export class MetadataExtractionProcessor {
});
}
} catch (error) {
Logger.error(`Failed to trigger object detection pipe line ${error.toString()}`);
Logger.error(`Failed to trigger object detection pipe line ${String(error)}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know there is such method :O wow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙂 it's handy in this case, where error could be anything, not just an Error instance with a toString method.


if (file.fieldname == 'assetData') {
const originalUploadFolder = `${basePath}/${req.user['id']}/original/${req.body['deviceId']}`;
const originalUploadFolder = `${basePath}/${req.user.id}/original/${req.body['deviceId']}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I got this error when trying to run the app

Screen Shot 2022-06-24 at 22 01 46

And the type of user here is User from Express.User
Screen Shot 2022-06-24 at 22 01 40

Copy link
Contributor

@alextran1502 alextran1502 Jun 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user here is returned from the function validate in jwt.strategy.ts which is a UserEntity
Screen Shot 2022-06-24 at 22 18 19

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my proposed fix for this issue in asset-upload.config.ts and profile-image-upload.config.ts

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes sorry about that, the e2e test was working in my computer (this is also the reason why e2e tests failed in CI) because the container was already created and still had the old tsconfig inside, so no strict mode 😅.

I have added a much neater fix, but would probably need to move the global.d.ts somewhere inside src, I'll take a look at fixing this.
Using as should be the last resort in TypeScript (also any but this is a subject for another day 😄)

I had added a short explanation in the git commit message:

override Express/User interface to use UserResponseDto interface
     This is for when the accessing the `user` from a Express Request,
     like in `asset-upload-config`

So Express left the User interface empty, so you can override it with your own interface like

declare global {
  namespace Express {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface User extends UserResponseDto {}
  }
}

I've used UserResponseDto for now, although we can create a specific interface for the user returned in jwt.strategy

This is now of type `string` that defaults to '0:00:00.00000' if not set
which is what the mobile app currently expects
Use `ServeFile` as the context for logging an error when
asset.resizePath is not set
`redirectToWebpage` was removed in main as is no longer used.
@jbaez
Copy link
Contributor Author

jbaez commented Jun 25, 2022

Thank you for another high-quality PR to improve the code base! I've left some comments and an idea to fix some issues while running on the dev server. Let me know what do you think

My pleasure. I've fixed the issues and replied the comments. Let me know your thoughts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants