chore(lint): switch to eslint

This commit is contained in:
Julian Tölle 2021-05-25 18:12:42 +02:00
parent f56548e432
commit 9b96d0fab4
29 changed files with 1609 additions and 113 deletions

43
.eslintrc.js Normal file
View file

@ -0,0 +1,43 @@
module.exports = {
extends: ["airbnb-typescript", "prettier"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
},
plugins: [
"eslint-plugin-import",
"eslint-plugin-jsdoc",
"eslint-plugin-prefer-arrow",
"eslint-plugin-react",
"@typescript-eslint",
],
rules: {
"import/prefer-default-export": "off",
"class-methods-use-this": "off",
"@typescript-eslint/lines-between-class-members": [
"error",
"always",
{ exceptAfterSingleLine: true },
],
"@typescript-eslint/return-await": "off",
"import/no-cycle": "off",
"no-restricted-syntax": [
"error",
{
selector: "ForInStatement",
message:
"for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
},
{
selector: "LabeledStatement",
message:
"Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
},
{
selector: "WithStatement",
message:
"`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
},
],
},
};

View file

@ -54,6 +54,7 @@ function useProvideAuth(): AuthContext {
useEffect(() => { useEffect(() => {
refreshAccessToken().catch(() => { refreshAccessToken().catch(() => {
// eslint-disable-next-line no-console
console.log("Unable to refresh access token"); console.log("Unable to refresh access token");
}); });
}, [refreshAccessToken]); }, [refreshAccessToken]);

View file

@ -1,3 +1,4 @@
/* eslint-disable no-console */
// This optional code is used to register a service worker. // This optional code is used to register a service worker.
// register() is not called by default. // register() is not called by default.

1550
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
"lint": "tslint -p tsconfig.json -c tslint.json", "lint": "eslint --ext .js,.jsx,.ts,.tsx src/ frontend/src/",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
@ -64,6 +64,17 @@
"@types/node": "15.6.0", "@types/node": "15.6.0",
"@types/passport-jwt": "3.0.5", "@types/passport-jwt": "3.0.5",
"@types/supertest": "2.0.11", "@types/supertest": "2.0.11",
"@typescript-eslint/eslint-plugin": "4.25.0",
"@typescript-eslint/parser": "4.25.0",
"eslint": "7.27.0",
"eslint-config-airbnb-typescript": "12.3.1",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-import": "2.23.3",
"eslint-plugin-jsdoc": "35.0.0",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react-hooks": "4.2.0",
"jest": "26.6.3", "jest": "26.6.3",
"prettier": "2.3.0", "prettier": "2.3.0",
"supertest": "6.1.3", "supertest": "6.1.3",
@ -71,9 +82,6 @@
"ts-loader": "9.2.2", "ts-loader": "9.2.2",
"ts-node": "10.0.0", "ts-node": "10.0.0",
"tsconfig-paths": "3.9.0", "tsconfig-paths": "3.9.0",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"tslint-plugin-prettier": "2.3.0",
"typescript": "4.2.4" "typescript": "4.2.4"
}, },
"jest": { "jest": {

View file

@ -12,7 +12,7 @@ export class AuthSession {
@PrimaryGeneratedColumn("uuid") @PrimaryGeneratedColumn("uuid")
id: string; id: string;
@ManyToOne((type) => User, { eager: true }) @ManyToOne(() => User, { eager: true })
user: User; user: User;
@CreateDateColumn() @CreateDateColumn()

View file

@ -1,4 +1,4 @@
// tslint:disable: max-classes-per-file /* eslint-disable max-classes-per-file */
import { EntityRepository, Repository, SelectQueryBuilder } from "typeorm"; import { EntityRepository, Repository, SelectQueryBuilder } from "typeorm";
import { User } from "../users/user.entity"; import { User } from "../users/user.entity";
import { AuthSession } from "./auth-session.entity"; import { AuthSession } from "./auth-session.entity";

View file

@ -7,7 +7,7 @@ import {
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { Response } from "express"; import type { Response } from "express";
import { User } from "../users/user.entity"; import { User } from "../users/user.entity";
import { AuthSession } from "./auth-session.entity"; import { AuthSession } from "./auth-session.entity";
import { AuthService } from "./auth.service"; import { AuthService } from "./auth.service";

View file

@ -4,7 +4,7 @@ import {
ExceptionFilter, ExceptionFilter,
ForbiddenException, ForbiddenException,
} from "@nestjs/common"; } from "@nestjs/common";
import { Response } from "express"; import type { Response } from "express";
import { Logger } from "../logger/logger.service"; import { Logger } from "../logger/logger.service";
@Catch() @Catch()

View file

@ -1,11 +1,12 @@
import { Controller, Get } from "@nestjs/common"; import { Controller, Get } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { import {
DNSHealthIndicator, DNSHealthIndicator,
HealthCheck, HealthCheck,
HealthCheckResult,
HealthCheckService, HealthCheckService,
TypeOrmHealthIndicator, TypeOrmHealthIndicator,
} from "@nestjs/terminus"; } from "@nestjs/terminus";
import { ConfigService } from "@nestjs/config";
@Controller("api/v1/health") @Controller("api/v1/health")
export class HealthCheckController { export class HealthCheckController {
@ -18,7 +19,7 @@ export class HealthCheckController {
@Get() @Get()
@HealthCheck() @HealthCheck()
check() { check(): Promise<HealthCheckResult> {
return this.health.check([ return this.health.check([
() => () =>
this.dns.pingCheck( this.dns.pingCheck(

View file

@ -1,9 +1,8 @@
/* eslint-disable max-classes-per-file */
import { Track } from "../../music-library/track.entity"; import { Track } from "../../music-library/track.entity";
import { User } from "../../users/user.entity"; import { User } from "../../users/user.entity";
import { Listen } from "../listen.entity"; import { Listen } from "../listen.entity";
// tslint:disable max-classes-per-file
export class CreateListenRequestDto { export class CreateListenRequestDto {
track: Track; track: Track;
user: User; user: User;

View file

@ -1,23 +1,22 @@
/* eslint-disable max-classes-per-file */
import { IsDate, IsOptional, ValidateNested } from "class-validator"; import { IsDate, IsOptional, ValidateNested } from "class-validator";
import { Interval } from "date-fns"; import { Interval } from "date-fns";
import { User } from "../../users/user.entity"; import { User } from "../../users/user.entity";
// tslint:disable-next-line: max-classes-per-file
export class GetListensFilterTimeDto implements Interval { export class GetListensFilterTimeDto implements Interval {
@IsDate() @IsDate()
start: Date; start: Date;
@IsDate() @IsDate()
end: Date; end: Date;
} }
// tslint:disable-next-line: max-classes-per-file
export class GetListensFilterDto { export class GetListensFilterDto {
@IsOptional() @IsOptional()
@ValidateNested() @ValidateNested()
time?: GetListensFilterTimeDto; time?: GetListensFilterTimeDto;
} }
// tslint:disable-next-line: max-classes-per-file
export class GetListensDto { export class GetListensDto {
user: User; user: User;

View file

@ -14,10 +14,10 @@ export class Listen {
@PrimaryGeneratedColumn("uuid") @PrimaryGeneratedColumn("uuid")
id: string; id: string;
@ManyToOne((type) => Track) @ManyToOne(() => Track)
track: Track; track: Track;
@ManyToOne((type) => User) @ManyToOne(() => User)
user: User; user: User;
@Column({ type: "timestamp" }) @Column({ type: "timestamp" })

View file

@ -1,4 +1,4 @@
// tslint:disable: max-classes-per-file /* eslint-disable max-classes-per-file */
import { EntityRepository, Repository, SelectQueryBuilder } from "typeorm"; import { EntityRepository, Repository, SelectQueryBuilder } from "typeorm";
import { Interval } from "../reports/interval"; import { Interval } from "../reports/interval";
import { User } from "../users/user.entity"; import { User } from "../users/user.entity";

View file

@ -19,8 +19,13 @@ export class ListensController {
@Query("filter") filter: GetListensFilterDto, @Query("filter") filter: GetListensFilterDto,
@ReqUser() user: User @ReqUser() user: User
): Promise<Pagination<Listen>> { ): Promise<Pagination<Listen>> {
limit = limit > 100 ? 100 : limit; const clampedLimit = limit > 100 ? 100 : limit;
return this.listensService.getListens({ page, limit, user, filter }); return this.listensService.getListens({
page,
limit: clampedLimit,
user,
filter,
});
} }
} }

View file

@ -3,9 +3,9 @@ import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core"; import { NestFactory } from "@nestjs/core";
import { NestExpressApplication } from "@nestjs/platform-express"; import { NestExpressApplication } from "@nestjs/platform-express";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { AppModule } from "./app.module";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { RavenInterceptor } from "nest-raven"; import { RavenInterceptor } from "nest-raven";
import { AppModule } from "./app.module";
function setupSentry( function setupSentry(
app: NestExpressApplication, app: NestExpressApplication,

View file

@ -18,13 +18,13 @@ export class Album {
@Column() @Column()
name: string; name: string;
@ManyToMany((type) => Artist, (artist) => artist.albums) @ManyToMany(() => Artist, (artist) => artist.albums)
@JoinTable({ name: "album_artists" }) @JoinTable({ name: "album_artists" })
artists?: Artist[]; artists?: Artist[];
@OneToMany((type) => Track, (track) => track.album) @OneToMany(() => Track, (track) => track.album)
tracks?: Track[]; tracks?: Track[];
@Column((type) => SpotifyLibraryDetails) @Column(() => SpotifyLibraryDetails)
spotify: SpotifyLibraryDetails; spotify: SpotifyLibraryDetails;
} }

View file

@ -10,9 +10,9 @@ export class Artist {
@Column() @Column()
name: string; name: string;
@ManyToMany((type) => Album, (album) => album.artists) @ManyToMany(() => Album, (album) => album.artists)
albums?: Album[]; albums?: Album[];
@Column((type) => SpotifyLibraryDetails) @Column(() => SpotifyLibraryDetails)
spotify: SpotifyLibraryDetails; spotify: SpotifyLibraryDetails;
} }

View file

@ -18,13 +18,13 @@ export class Track {
@Column() @Column()
name: string; name: string;
@ManyToOne((type) => Album, (album) => album.tracks) @ManyToOne(() => Album, (album) => album.tracks)
album?: Album; album?: Album;
@ManyToMany((type) => Artist) @ManyToMany(() => Artist)
@JoinTable({ name: "track_artists" }) @JoinTable({ name: "track_artists" })
artists?: Artist[]; artists?: Artist[];
@Column((type) => SpotifyLibraryDetails) @Column(() => SpotifyLibraryDetails)
spotify?: SpotifyLibraryDetails; spotify?: SpotifyLibraryDetails;
} }

View file

@ -1,5 +1,3 @@
import { Timeframe } from "../timeframe.enum";
export class ListenReportDto { export class ListenReportDto {
items: { items: {
date: string; date: string;

View file

@ -264,6 +264,13 @@ export class ReportsService {
break; break;
} }
default: {
interval = this.getIntervalFromPreset({
timePreset: TimePreset.LAST_7_DAYS,
});
break;
}
} }
return interval; return interval;

View file

@ -1,8 +1,4 @@
import { import { Injectable, OnApplicationBootstrap } from "@nestjs/common";
Injectable,
OnApplicationBootstrap,
OnModuleInit,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { SchedulerRegistry } from "@nestjs/schedule"; import { SchedulerRegistry } from "@nestjs/schedule";
import { captureException } from "@sentry/node"; import { captureException } from "@sentry/node";

View file

@ -1,5 +1,4 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { ConfigModule } from "../config/config.module";
import { SchedulerService } from "./scheduler.service"; import { SchedulerService } from "./scheduler.service";
import { SpotifyModule } from "./spotify/spotify.module"; import { SpotifyModule } from "./spotify/spotify.module";

View file

@ -12,7 +12,6 @@ export class SpotifyApiService {
async getRecentlyPlayedTracks({ async getRecentlyPlayedTracks({
accessToken, accessToken,
lastRefreshTime,
}: SpotifyConnection): Promise<PlayHistoryObject[]> { }: SpotifyConnection): Promise<PlayHistoryObject[]> {
const parameters: { limit: number; after?: number } = { const parameters: { limit: number; after?: number } = {
limit: 50, limit: 50,

View file

@ -35,6 +35,8 @@ export class SpotifyService {
const users = await this.usersService.findAll(); const users = await this.usersService.findAll();
for (const user of users) { for (const user of users) {
// We want to run this sequentially to avoid rate limits
// eslint-disable-next-line no-await-in-loop
await this.crawlListensForUser(user); await this.crawlListensForUser(user);
} }
} }
@ -142,7 +144,7 @@ export class SpotifyService {
spotifyID spotifyID
); );
} catch (err) { } catch (err) {
if (err.response && err.response.status === 401) { if (err.response && err.response.status === 401 && retryOnExpiredToken) {
await this.refreshAppAccessToken(); await this.refreshAppAccessToken();
return this.importTrack(spotifyID, false); return this.importTrack(spotifyID, false);
@ -192,7 +194,7 @@ export class SpotifyService {
spotifyID spotifyID
); );
} catch (err) { } catch (err) {
if (err.response && err.response.status === 401) { if (err.response && err.response.status === 401 && retryOnExpiredToken) {
await this.refreshAppAccessToken(); await this.refreshAppAccessToken();
return this.importAlbum(spotifyID, false); return this.importAlbum(spotifyID, false);
@ -238,7 +240,7 @@ export class SpotifyService {
spotifyID spotifyID
); );
} catch (err) { } catch (err) {
if (err.response && err.response.status === 401) { if (err.response && err.response.status === 401 && retryOnExpiredToken) {
await this.refreshAppAccessToken(); await this.refreshAppAccessToken();
return this.importArtist(spotifyID, false); return this.importArtist(spotifyID, false);
@ -261,6 +263,8 @@ export class SpotifyService {
private async refreshAppAccessToken(): Promise<void> { private async refreshAppAccessToken(): Promise<void> {
if (!this.appAccessTokenInProgress) { if (!this.appAccessTokenInProgress) {
this.logger.debug("refreshing spotify app access token"); this.logger.debug("refreshing spotify app access token");
/* eslint-disable no-async-promise-executor */
this.appAccessTokenInProgress = new Promise(async (resolve, reject) => { this.appAccessTokenInProgress = new Promise(async (resolve, reject) => {
try { try {
const newAccessToken = const newAccessToken =

View file

@ -1,4 +1,4 @@
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"; import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { SpotifyConnection } from "../sources/spotify/spotify-connection.entity"; import { SpotifyConnection } from "../sources/spotify/spotify-connection.entity";
@Entity() @Entity()
@ -12,6 +12,6 @@ export class User {
@Column({ nullable: true }) @Column({ nullable: true })
photo?: string; photo?: string;
@Column((type) => SpotifyConnection) @Column(() => SpotifyConnection)
spotify: SpotifyConnection; spotify: SpotifyConnection;
} }

View file

@ -49,6 +49,7 @@ export class UsersService {
user: User, user: User,
spotify: SpotifyConnection spotify: SpotifyConnection
): Promise<void> { ): Promise<void> {
// eslint-disable-next-line no-param-reassign
user.spotify = spotify; user.spotify = spotify;
await this.userRepository.save(user); await this.userRepository.save(user);
} }

View file

@ -8,7 +8,7 @@ describe("AppController (e2e)", () => {
beforeEach(async () => { beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({ const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule] imports: [AppModule],
}).compile(); }).compile();
app = moduleFixture.createNestApplication(); app = moduleFixture.createNestApplication();

View file

@ -1,23 +0,0 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended",
"tslint-config-prettier",
"tslint-plugin-prettier"
],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"member-access": [false],
"ordered-imports": [false],
"max-line-length": [true, 150],
"member-ordering": [false],
"interface-name": [false],
"arrow-parens": false,
"object-literal-sort-keys": false,
"trailing-comma": false,
"prettier": "true"
},
"rulesDirectory": []
}