Skip to content

Commit

Permalink
document code style guidelines
Browse files Browse the repository at this point in the history
  • Loading branch information
Metauriel committed Nov 17, 2023
1 parent 3c21cfe commit 3b770b6
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 2 deletions.
45 changes: 45 additions & 0 deletions docs/schulcloud-server/Coding-Guidelines/access-legacy-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Access legacy Code

## Access Feathers Service from NestJS

The `FeathersModule` provides functionality to access legacy code. In order to introduce strong typing, it is necessary to write an adapter service for the feathers service you want to access. Place this adapter within your module, and use the `FeathersServiceProvider` to access the service you need

```TypeScript
// inside your module, import the FeathersModule
@Module({
imports: [FeathersModule],
providers: [MyFeathersServiceAdapter],
})
export class MyModule {}

// inside of your service, inject the FeathersServiceProvider
@Injectable()
export class MyFeathersServiceAdapter {
constructor(private feathersServiceProvider: FeathersServiceProvider) {}

async get(): Promise<string[]> {
const service = this.feathersServiceProvider.getService(`path`);
const result = await service.get(...)

return result;
}

```
## Access NestJS injectable from Feathers
To access a NestJS service from a legacy Feathers service you need to make the NestJS service known to the Feathers service-collection in `main.ts`.
This possibility should not be used for new features in Feathers, but it can help if you want to refactor a Feathers service to NestJs although other Feathers services depend on it.
```TypeScript
// main.ts
async function bootstrap() {
// (...)
feathersExpress.services['nest-rocket-chat'] = nestApp.get(RocketChatService);
// (...)
}
```
Afterwards you can access it the same way as you access other Feathers services with
`app.service('/nest-rocket-chat');`
82 changes: 80 additions & 2 deletions docs/schulcloud-server/Coding-Guidelines/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Function

### Naming
### Naming functions

The name of a function should clearly communicate what it does. There should be no need to read the implementation of a function to understand what it does.

Expand All @@ -28,6 +28,72 @@ A function with the prefix "check..." is checking the condition described in its

similar to "is...", the prefix "has..." means that the function is checking a condition, and returns a boolean. It does NOT throw an error.

### Avoid direct returns of computations

avoid directly returning the result of some computation. Instead, use a variable to give the result of the computation a name.

Exceptions can be made when the result of the computation is already clear from the function name, and the function is sufficiently simple to not occlude its meaning.

```typescript
public doSomething(): FileRecordParams[] {
// ... more logic here
const fileRecordParams = fileRecords.map((fileRecord) => Mapper.toParams(fileRecord));
// hint: this empty line can be increase the readability
return fileRecordParams;
}

public getName(): String {
return this.name;
}

public getInfo(): IInfo {
// ... more logic here
return { name, parentId, parentType }; // but if the return include many keys, please put it first to a const
}
```

### avoid directly passing function results as parameters

```typescript
function badExample(): void {
doSomething(this.extractFromParams(params), this.createNewConfiguration());
}

function goodExample(): void {
const neededParams = this.extractFromParams(params);
const configuration = this.createNewConfiguration();
doSomething(neededParams, configuration);
}
```

### explicit return type

```typescript
public doSomething(): FileRecords[] {
//...
return fileRecords
}
```

## Interfaces

### Avoid the "I" for interfaces

In most cases, it should not matter to the reader/external code wether something is an interface or an implementation. Only prefix the "I" when necessary, or when its specifically important to the external code to know that its an interface, for example when external code is required to implement the interface.

```Typescript
interface CourseRepo {
getById(id): Course
// ...
}

class InMemoryCourseRepo implements CourseRepo {
getById(id): Course {
return new Course()
}
}
```

## Classes

### Order of declarations
Expand All @@ -53,10 +119,22 @@ export class Course {
}

// 3. methods
getShortTitle(): string {
public getShortTitle(): string {
// ...
}

// more methods...
}
```

### Naming classes

Classes should be named in CamelCase. They should have a Suffix with the kind of Object they represent, and from the beginning to the end go from specific to general.

- CourseUc



## Do not use JsDoc

You should always try to write code in a way that does not require further explanation to understand. Use proper names for functions and variables, and extract code and partial results into functions or variables in order to name them. If you feel like a function needs a JsDoc, treat that as a codesmell, and try to rewrite the code in a way that is more self-explanatory.
49 changes: 49 additions & 0 deletions docs/schulcloud-server/Coding-Guidelines/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Controller

A modules api layer is defined within of [controllers](https://docs.nestjs.com/controllers).

The main responsibilities of a controller is to define the REST API interface as openAPI specification and map DTO's to match the logic layers interfaces.

```TypeScript
@Post()
async create(@CurrentUser() currentUser: ICurrentUser, @Body() params: CreateNewsParams): Promise<NewsResponse> {
const news = await this.newsUc.create(
currentUser.userId,
currentUser.schoolId,
NewsMapper.mapCreateNewsToDomain(params)
);
const dto = NewsMapper.mapToResponse(news);
return dto;
}
```

## JWT-Authentication

For **authentication**, use [guards](https://docs.nestjs.com/guards) like JwtAuthGuard. It can be applied to a whole controller or a single controller method only. Then, [ICurrentUser](/apps/server/src/modules/authentication/interface/jwt-payload.ts) can be injected using the `@CurrentUser()` decorator.

## Validation

Global settings of the core-module ensure **request/response validation** against the api definition. Simple input types might additionally use a custom [pipe](https://docs.nestjs.com/pipes) while for complex types injected as query/body are validated by default when parsed as DTO class.

## DTO File naming

Complex input DTOs are defined like [create-news].params.ts (class-name: CreateNewsParams).

When DTO's are shared between multiple modules, locate them in the layer-related shared folder.

> **Security:** When exporting data, internal entities must be mapped to a response DTO class named like [news].response.dto. The mapping ensures which data of internal entities are exported.
## openAPI specification

Defining the request/response DTOs in a controller will define the openAPI specification automatically. Additional [validation rules](https://docs.nestjs.com/techniques/validation) and [openAPI definitions](https://docs.nestjs.com/openapi/decorators) can be added using decorators. For simplification, openAPI decorators should define a type and if a property is required, while additional decorators can be used from class-validator to validate content.

## Mapping

It is forbidden, to directly pass a DTO to a use-case or return an Entity (or other use-case result) via REST. In-between a mapper must transform the given data, to protect the logic layer from outside implications.

The use of a mapper gives us the guarantee, that

- no additional data beside the known properties is published.
- A plain object might contain more properties than defined in TS-interfaces.
Sample: All school properties are published while only name & id are intended to be published.
- the API definition is complete

0 comments on commit 3b770b6

Please sign in to comment.