...
 
Commits (4)
This diff is collapsed.
......@@ -24,7 +24,7 @@ export class AppComponent implements OnInit {
public timingTest() {
const start = new Date().getTime();
this.httpClient.get('https://us-central1-codecentric-firebase-workshop.cloudfunctions.net/echo')
this.httpClient.get('https://europe-west1-codecentric-firebase-workshop.cloudfunctions.net/https-echo')
.subscribe(message => {
this.message = JSON.stringify(message);
const end = new Date().getTime();
......
......@@ -22,6 +22,7 @@
</ng-template>
<ng-template #deleteAction>
<!-- FixMe: Step 3.1: Toggle comments of these 2 divs -->
<!--<div (click)="onDelete()"><i class="anticon anticon-delete"></i></div>-->
<div *ngIf="(uid$ | async) === imageData.user" (click)="onDelete()"><i nz-icon nzType="delete" nzTheme="outline"></i>
</div>
......
......@@ -7,6 +7,7 @@ export const environment = {
databaseURL: 'https://codecentric-firebase-workshop.firebaseio.com',
projectId: 'codecentric-firebase-workshop',
storageBucket: 'codecentric-firebase-workshop.appspot.com',
messagingSenderId: '239116800704'
messagingSenderId: '239116800704',
appId: '1:239116800704:web:971c2c2ad7f718bd'
}
};
......@@ -16,5 +16,5 @@ exports.storage = {
exports.firestore = {
imageDeleted: firestoreImageDeletedTrigger,
imageVoteWrite: firestoreImageVoteWriteTrigger
imageVoteWrite: firestoreImageVoteWriteTrigger // Inspiration for part 3.4
};
{
"name": "functions",
"scripts": {
"lint": "./node_modules/.bin/tslint -p tslint.json",
"mono:model": "copy -S ../model/src -D .mono/model -E .spec. -H --verbose 2",
"mono:quick": "yarn --ignore-engines mono:model -Q",
"mono:full": "rimraf .mono && yarn --ignore-engines mono:model",
"compile": "./node_modules/.bin/tsc",
"build": "yarn --ignore-engines lint && yarn --ignore-engines compile",
"build:prod": "yarn --ignore-engines build",
"serve": "yarn --ignore-engines build && firebase serve --only functions",
"shell": "yarn --ignore-engines build && firebase experimental:functions:shell",
"start": "yarn --ignore-engines shell",
"deploy": "firebase deploy --only functions",
"echo": "firebase deploy --only functions:voteAggregate",
"logs": "firebase functions:log",
"clean": "rimraf ./node_modules && rimraf ./lib"
},
"engines": {
"node": "10"
},
"main": "lib/src/index.js",
"dependencies": {
"firebase-admin": "^8.3.0",
"cors": "^2.8.5",
"firebase-functions": "^3.2.0",
"jimp": "0.6.4"
},
"devDependencies": {
"@endran/copy": "^0.3.2",
"tslint": "^5.18.0",
"typescript": "^3.5.3",
"rimraf": "^2.6.3"
},
"private": true
}
import {OperatorFunction} from 'rxjs';
export interface Command {
pipeline: OperatorFunction<any, any>[];
execute(...args: any[]): Promise<any>;
}
import {Command} from './command';
import * as admin from "firebase-admin";
export class EchoCommand implements Command {
async execute(id: number): Promise<string> {
const snapshot = await admin.firestore().doc(`test/test${id}`).get();
const count = (snapshot.data() || {count: 0}).count + 1;
await admin.firestore().doc(`test/test${id}`).set({count});
return `{"message": "Hello from Firebase!", "count":${count}, "id":${id}}`;
}
}
import {Command} from './command';
import {OperatorFunction} from 'rxjs';
import {switchMap, tap} from 'rxjs/operators';
import {StorageService} from '../facade/storage.service';
import {Logger} from '../environment/logger';
import {DatabaseService} from '../environment/database.service';
import {ImageData} from "../../.mono/model/image-data.model";
import {StorageService} from "../environment/storage.service";
export class ImageDataDeletedCommand implements Command {
public static PATH = 'images/{imageId}';
constructor(private storageService: StorageService, private databaseService: DatabaseService, private log: Logger) {
}
constructor(private imageData: any, private storageService: StorageService, private log: Logger) {
}
async execute(imageData: ImageData): Promise<void> {
this.log.debug(`ImageDataDeletedCommand: Deleting Votes`);
await this.databaseService.deleteVoting(imageData.id);
pipeline: OperatorFunction<any, any>[] = [
tap(() => this.log.debug(`ImageDataDeletedCommand: Begin`)),
this.log.debug(`ImageDataDeletedCommand: Deleting:${imageData.imgRef}`);
await this.storageService.deleteFile(imageData.imgRef);
tap(() => this.log.debug(`ImageDataDeletedCommand: Deleting:${this.imageData.imgRef}`)),
switchMap(() => this.storageService.deleteFile(this.imageData.imgRef)),
this.log.debug(`ImageDataDeletedCommand: Deleting:${imageData.thumbRef}`);
await this.storageService.deleteFile(imageData.thumbRef);
tap(() => this.log.debug(`ImageDataDeletedCommand: Deleting:${this.imageData.thumbRef}`)),
switchMap(() => this.storageService.deleteFile(this.imageData.thumbRef)),
tap(() => this.log.debug(`ImageDataDeletedCommand: Done`))
];
this.log.debug(`ImageDataDeletedCommand: Done`);
}
}
import {from, MonoTypeOperatorFunction, OperatorFunction} from 'rxjs';
import {ObjectMetadata} from 'firebase-functions/lib/providers/storage';
import {map, switchMap, tap} from 'rxjs/operators';
import {Command} from './command';
import {PlatformEnvironment} from '../environment/platform-environment';
import {StorageService} from '../facade/storage.service';
import {DatabaseService} from '../facade/database.service';
import {DatabaseService} from '../environment/database.service';
import Jimp = require('jimp');
import {StorageService} from "../environment/storage.service";
export class ImageUploadedCommand implements Command {
private imageId: string;
private sourceRemoteRef: string;
private thumbRemoteRef: string;
private sourceFilePath: string;
private thumbFilePath: string;
static CAN_EXECUTE(object: ObjectMetadata): boolean {
return object.name.split('/')[0] === 'images' && object.name.indexOf('thumb_') < 0;
}
constructor(private object: ObjectMetadata,
private storageService: StorageService,
private databaseService: DatabaseService,
private env: PlatformEnvironment = new PlatformEnvironment()) {
}
get pipeline(): OperatorFunction<any, any>[] {
const imgName = this.env.path.basename(this.object.name);
this.imageId = imgName.split('.')[0];
this.sourceRemoteRef = this.object.name;
this.thumbRemoteRef = this.object.name.replace(imgName, `thumb_${imgName}`);
this.sourceFilePath = this.env.path.join(this.env.os.tmpdir(), imgName);
this.thumbFilePath = this.env.path.join(this.env.os.tmpdir(), `thumb_${imgName}`);
return [
this.logStart(),
this.writeImageRefToFirestore(),
this.downloadImageToFile(),
this.loadFileWithJimp(),
this.createThumbnail(),
this.uploadThumbnailToBucket(),
this.writeThumbRefToFirestore(),
this.freeUpLocalFiles(),
this.getSharableLinkFromGCloudForThumb(),
this.writeThumbLinkToFirestore(),
this.getSharableLinkFromGCloudForOriginalImage(),
this.writeOriginalLinkToFirestore(),
this.logDone()
];
}
private logStart() {
return tap(() => this.env.log.info(`ImageUploadedCommand: start`));
}
private downloadImageToFile() {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: downloadImageToFile`);
return from(this.storageService.downloadFile(this.sourceRemoteRef, this.sourceFilePath));
});
}
private loadFileWithJimp(): OperatorFunction<any, Jimp.Jimp> {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: loadFileWithJimp`);
return from(Jimp.read(this.sourceFilePath));
});
}
private createThumbnail(): OperatorFunction<Jimp.Jimp, Jimp.Jimp> {
return map((jimp: Jimp.Jimp) => {
this.env.log.debug(`ImageUploadedCommand: createThumbnail`);
return jimp.cover(192, 192, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
.write(this.thumbFilePath);
});
}
private uploadThumbnailToBucket() {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: uploadThumbnailToBucket`);
return from(this.storageService.upload(this.thumbFilePath, this.thumbRemoteRef));
});
};
private freeUpLocalFiles() {
return tap(() => {
this.env.log.debug(`ImageUploadedCommand: freeUpLocalFiles`);
this.env.fs.unlinkSync(this.sourceFilePath);
this.env.fs.unlinkSync(this.thumbFilePath);
});
};
private getSharableLinkFromGCloudForThumb(): OperatorFunction<any, string> {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: getSharableLinkFromGCloudForThumb`);
return from(this.storageService.getPublicUrlToFile(this.thumbRemoteRef));
});
};
private writeThumbLinkToFirestore(): OperatorFunction<any, any> {
return switchMap(url => {
this.env.log.debug(`ImageUploadedCommand: writeThumbLinkToFirestore`);
return from(this.databaseService.mergeImageData(this.imageId, {thumbUrl: url}));
});
};
private getSharableLinkFromGCloudForOriginalImage(): OperatorFunction<any, string> {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: getSharableLinkFromGCloudForOriginalImage`);
return from(this.storageService.getPublicUrlToFile(this.sourceRemoteRef));
});
};
private writeOriginalLinkToFirestore(): OperatorFunction<any, any> {
return switchMap(url => {
this.env.log.debug(`ImageUploadedCommand: writeOriginalLinkToFirestore`);
return from(this.databaseService.mergeImageData(this.imageId, {imgUrl: url}));
});
};
private writeImageRefToFirestore(): OperatorFunction<any, any> {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: writeImageRefToFirestore`);
return from(this.databaseService.mergeImageData(this.imageId, {imgRef: this.sourceRemoteRef}));
});
}
private writeThumbRefToFirestore(): OperatorFunction<any, any> {
return switchMap(() => {
this.env.log.debug(`ImageUploadedCommand: writeThumbRefToFirestore`);
return from(this.databaseService.mergeImageData(this.imageId, {thumbRef: this.thumbRemoteRef}));
});
}
private logDone(): MonoTypeOperatorFunction<any> {
return tap(() => this.env.log.info(`ImageUploadedCommand: done`));
}
constructor(private storageService: StorageService,
private databaseService: DatabaseService,
private env: PlatformEnvironment = new PlatformEnvironment()) {
}
async execute(sourceCloudPath: string): Promise<void> {
this.env.log.info(`ImageUploadedCommand: start`);
if(sourceCloudPath.split('/')[0] !== 'images' || sourceCloudPath.indexOf('thumb_') >= 0) {
this.env.log.debug(`ImageUploadedCommand: no action required for ${sourceCloudPath}`);
return;
}
const imgName = this.env.path.basename(sourceCloudPath);
const imageId = imgName.split('.')[0];
const thumbCloudPath = sourceCloudPath.replace(imgName, `thumb_${imgName}`);
const sourceLocalPath = this.env.path.join(this.env.os.tmpdir(), imgName);
const thumbLocalPath = this.env.path.join(this.env.os.tmpdir(), `thumb_${imgName}`);
this.env.log.debug(`ImageUploadedCommand: imgName=${imgName}`);
this.env.log.debug(`ImageUploadedCommand: imageId=${imageId}`);
this.env.log.debug(`ImageUploadedCommand: thumbCloudPath=${thumbCloudPath}`);
this.env.log.debug(`ImageUploadedCommand: sourceLocalPath=${sourceLocalPath}`);
this.env.log.debug(`ImageUploadedCommand: thumbLocalPath=${thumbLocalPath}`);
this.env.log.debug(`ImageUploadedCommand: writeImageRefToFirestore`);
await this.databaseService.mergeImageData(imageId, {imgRef: sourceCloudPath});
this.env.log.debug(`ImageUploadedCommand: downloadImageToFile`);
await this.storageService.downloadFile(sourceCloudPath, sourceLocalPath);
this.env.log.debug(`ImageUploadedCommand: loadFileWithJimp`);
const jimp = await Jimp.read(sourceLocalPath);
this.env.log.debug(`ImageUploadedCommand: createThumbnail`);
jimp.cover(192, 192, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
.write(thumbLocalPath);
this.env.log.debug(`ImageUploadedCommand: uploadThumbnailToBucket`);
await this.storageService.uploadFile(thumbLocalPath, thumbCloudPath, {contentType: 'image/jpeg'});
this.env.log.debug(`ImageUploadedCommand: writeThumbRefToFirestore`);
await this.databaseService.mergeImageData(imageId, {thumbRef: thumbCloudPath});
this.env.log.debug(`ImageUploadedCommand: freeUpLocalFiles`);
this.env.fs.unlinkSync(sourceLocalPath);
this.env.fs.unlinkSync(thumbLocalPath);
this.env.log.debug(`ImageUploadedCommand: getSharableLinkFromGCloudForThumb`);
const thumbUrl = await this.storageService.getPublicUrlToFile(thumbCloudPath);
this.env.log.debug(`ImageUploadedCommand: writeThumbLinkToFirestore:${thumbUrl}`);
await this.databaseService.mergeImageData(imageId, {thumbUrl});
this.env.log.debug(`ImageUploadedCommand: getSharableLinkFromGCloudForOriginalImage`);
const sourceUrl = await this.storageService.getPublicUrlToFile(sourceCloudPath);
this.env.log.debug(`ImageUploadedCommand: writeOriginalLinkToFirestore:${sourceUrl}`);
await this.databaseService.mergeImageData(imageId, {imgUrl: sourceUrl});
this.env.log.info(`ImageUploadedCommand: done`);
}
}
import {Command} from './command';
import {Logger} from '../environment/logger';
import {DatabaseService} from '../environment/database.service';
import {VoteContainer} from "../../.mono/model/vote.model";
import {ImageData} from "../../.mono/model/image-data.model";
export class VoteCastedCommand implements Command {
constructor(private databaseService: DatabaseService,
private log: Logger) {
}
async execute(params: VoteCastedCommandData,
voteBefore: VoteContainer,
voteAfter: VoteContainer): Promise<void> {
this.log.debug(`VoteCastedCommand: Begin, params=${JSON.stringify(params)}, voteBefore=${JSON.stringify(voteBefore)}, voteAfter=${JSON.stringify(voteAfter)}`);
this.log.debug(`VoteCastedCommand: databaseService getImageVoteMetaData ${params.imageId}`);
const imageData = await this.databaseService.getImageData(params.imageId);
this.log.debug(`VoteCastedCommand: updateVoteAggregate old score: ${imageData.score}`);
const voteAggregate = this.updateVoteAggregate(imageData, voteBefore, voteAfter);
this.log.debug(`VoteCastedCommand: databaseService setImageVoteMetaData ${params.imageId} ${JSON.stringify(voteAggregate)}`);
await this.databaseService.mergeImageData(params.imageId, {
totalVotes: voteAggregate.newTotal,
score: voteAggregate.newScore
});
this.log.debug(`VoteCastedCommand: Done`);
}
private updateVoteAggregate(imageData: ImageData,
voteBefore: VoteContainer,
voteAfter: VoteContainer): { newTotal: number, newScore: number } {
const before = voteBefore ? voteBefore.vote : 'BLANK';
const after = voteAfter ? voteAfter.vote : 'BLANK';
let scoreMod = 0;
if (before === 'UP') {
scoreMod -= 1;
} else if (before === 'DOWN') {
scoreMod += 1;
}
if (after === 'UP') {
scoreMod += 1;
} else if (after === 'DOWN') {
scoreMod -= 1;
}
let totalMod = 0;
if (before === 'BLANK') {
totalMod = 1;
} else if (after === 'BLANK') {
totalMod = -1;
}
const oldTotal = imageData.totalVotes ? imageData.totalVotes : 0;
const oldScore = imageData.score ? imageData.score : 0;
const newTotal = oldTotal + totalMod;
const newScore = oldScore + scoreMod;
return {newTotal, newScore};
}
}
export interface VoteCastedCommandData {
imageId: string;
voteId: string;
}
import * as admin from 'firebase-admin';
import {ImageData} from "../../.mono/model/image-data.model";
export class DatabaseService {
async getImageData(imageId: string): Promise<ImageData> {
const snapshot = await admin.firestore().doc(`images/${imageId}`).get();
return snapshot.data();
}
async mergeImageData(imageId: string, data: ImageData): Promise<any> {
return admin.firestore().doc(`images/${imageId}`).set(data, {merge: true});
}
async deleteVoting(imageId: string) {
const ref = admin.firestore().collection(`images/${imageId}/meta`);
const batchSize = 100;
const query = ref.limit(batchSize);
return this.deleteQueryBatch(admin.firestore(), query);
}
private async deleteQueryBatch(db: admin.firestore.Firestore, query): Promise<any> {
const snapshot = await query.get();
if (snapshot.size === 0) {
return;
}
const batch = db.batch();
await Promise.all[snapshot.docs.map((doc) => {
batch.delete(doc.ref);
})];
const {numDeleted} = await batch.commit().then(() => {
return snapshot.size;
});
if (numDeleted === 0) {
return;
}
return await this.deleteQueryBatch(db, query);
}
}
const packageJson: any = require('../../package.json');
// const packageJson: any = require('../../package.json');
export class Logger {
private static VM_ID = (Math.random() * 1000000).toFixed();
private instanceId = (Math.random() * 1000000).toFixed();
private tag = `${(packageJson.git ? packageJson.git : {short: 'DEV'}).short}-${Logger.VM_ID}-${this.instanceId}: `;
private tag = `DEV-${Logger.VM_ID}-${this.instanceId}: `;
// private tag = `${(packageJson.git ? packageJson.git : {short: 'DEV'}).short}-${Logger.VM_ID}-${this.instanceId}: `;
public debug(message: any, noId: boolean = false) {
if (noId) {
......
import * as admin from 'firebase-admin';
export class StorageService {
private getBucket() {
return admin.storage().bucket();
}
async downloadFile(cloudPath: string, localPath: string): Promise<Buffer> {
const file = this.getBucket().file(cloudPath);
if (!file.exists()) {
throw Error(`File at ${cloudPath} does not exist`);
}
const downloadResponse = await file.download({destination: localPath});
return downloadResponse[0];
}
async deleteFile(path: string): Promise<void> {
const file = this.getBucket().file(path);
const exists: boolean[] = await file.exists();
if (exists && exists[0]) {
await file.delete();
}
}
async uploadFile(localPath: string, cloudPath: string, metadata: any={}): Promise<any> {
const uploadResponse = await this.getBucket().upload(localPath, {
destination: cloudPath,
metadata: metadata,
resumable: false
});
return uploadResponse[1];
}
async getPublicUrlToFile(remoteRef: string): Promise<string> {
const signedUrl = await this.getBucket().file(remoteRef).getSignedUrl({
action: 'read',
expires: '03-01-2500'
});
return signedUrl[0];
}
}
import * as admin from 'firebase-admin';
import {from, Observable} from 'rxjs';
export class DatabaseService {
mergeImageData(imageId: string, data: any): Observable<any> {
return from(admin.firestore().doc(`images/${imageId}`).set(data, {merge: true}));
}
}
import * as admin from 'firebase-admin';
import {from, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
export class StorageService {
downloadFile(remoteRef: string, localPath: string): Observable<any> {
return from(this.getBucket().file(remoteRef).download({destination: localPath}));
}
deleteFile(path: string): Observable<boolean> {
return from(this.getBucket().file(path).delete()).pipe(map(res => {
return res !== undefined;
}));
}
upload(localPath: string, remoteRef: string, metadata?: any): Observable<any> {
return from(this.getBucket().upload(localPath, {
destination: remoteRef,
metadata: metadata
}));
}
getPublicUrlToFile(remoteRef: string): Observable<string> {
return from(this.getBucket().file(remoteRef).getSignedUrl({
action: 'read',
expires: '03-01-2500'
})).pipe(map(res => res[0]));
}
private getBucket() {
return admin.storage().bucket();
}
}
import * as functions from 'firebase-functions';
import {StorageService} from './facade/storage.service';
import {take} from 'rxjs/operators';
import {Command} from './commands/command';
import {EMPTY, of} from 'rxjs';
import {ImageDataDeletedCommand} from './commands/image-data-deleted.command';
import {Logger} from './environment/logger';
import {ImageUploadedCommand} from './commands/image-uploaded.command';
import {DatabaseService} from './facade/database.service';
import {PlatformEnvironment} from './environment/platform-environment';
import * as admin from 'firebase-admin';
import {storageFinalizeTrigger} from "./triggers/storage.trigger";
import {httpEchoTrigger} from "./triggers/https.trigger";
import {firestoreImageDeletedTrigger} from "./triggers/firestore/firestore-images.trigger";
import {firestoreImageVoteWriteTrigger} from "./triggers/firestore/firestore-image-vote.trigger";
admin.initializeApp();
function executeCommand(command: Command): Promise<any> {
return of(1).pipe(
...command.pipeline,
take(1)
).toPromise();
}
exports.https = {
echo: httpEchoTrigger
};
export const storageFinalizeFunctions = functions.storage.object().onFinalize((object, context) => {
if (ImageUploadedCommand.CAN_EXECUTE(object)) {
return executeCommand(new ImageUploadedCommand(object, new StorageService(), new DatabaseService(), new PlatformEnvironment()));
} else {
console.log(`No command for object ${object.name}, with eventType ${context.eventType}`);
return EMPTY.toPromise();
}
});
exports.storage = {
finalize: storageFinalizeTrigger
};
export const deleteImages = functions.firestore.document(ImageDataDeletedCommand.PATH).onDelete((snapshot) => {
return executeCommand(new ImageDataDeletedCommand(snapshot.data(), new StorageService(), new Logger()));
});
exports.firestore = {
imageDeleted: firestoreImageDeletedTrigger,
imageVoteWrite: firestoreImageVoteWriteTrigger // Inspiration for part 3.4
};
import * as functions from "firebase-functions";
import {VoteCastedCommand, VoteCastedCommandData} from "../../commands/vote-casted.command";
import {VoteContainer} from "../../../.mono/model/vote.model";
import {DatabaseService} from "../../environment/database.service";
import {Logger} from "../../environment/logger";
export const firestoreImageVoteWriteTrigger = functions
.region('europe-west1')
.firestore.document('images/{imageId}/votes/{voteId}').onWrite(async (snapshot, context) => {
const databaseService = new DatabaseService();
const logger = new Logger();
const command = new VoteCastedCommand(databaseService, logger);
await command.execute(context.params as VoteCastedCommandData, snapshot.before.data() as VoteContainer, snapshot.after.data() as VoteContainer);
});
import * as functions from "firebase-functions";
import {ImageDataDeletedCommand} from "../../commands/image-data-deleted.command";
import {StorageService} from "../../environment/storage.service";
import {DatabaseService} from "../../environment/database.service";
import {Logger} from "../../environment/logger";
export const firestoreImageDeletedTrigger = functions
.region('europe-west1')
.firestore.document('images/{imageId}').onDelete(async snapshot => {
const storageService = new StorageService();
const databaseService = new DatabaseService();
const logger = new Logger();
const command = new ImageDataDeletedCommand(storageService, databaseService, logger);
await command.execute(snapshot.data());
});
import * as functions from "firebase-functions";
import {EchoCommand} from "../commands/echo.command";
const cors = require('cors')({origin: true});
export const httpEchoTrigger = functions
.region('europe-west1')
.https.onRequest(async (request, response) => {
const message = await new EchoCommand().execute(request.params['id'] || 0);
cors(request, response, () => {
response.status(200).send(message)
})
});
import * as functions from "firebase-functions";
import {ImageUploadedCommand} from "../commands/image-uploaded.command";
import {StorageService} from "../environment/storage.service";
import {DatabaseService} from "../environment/database.service";
import {PlatformEnvironment} from "../environment/platform-environment";
export const storageFinalizeTrigger = functions
.region('europe-west1')
.runWith({timeoutSeconds: 540, memory: '2GB'})
.storage.object().onFinalize((object) => {
const storageService = new StorageService();
const databaseService = new DatabaseService();
const platformEnvironment = new PlatformEnvironment();
const command = new ImageUploadedCommand(storageService, databaseService, platformEnvironment);
return command.execute(object.name);
});
{
"compilerOptions": {
"lib": [
"es6"
],
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "lib",
"sourceMap": true,
"target": "es6"
},
"compileOnSave": true,
"include": [
"src",
".mono/model/**/*.ts"
],
"exclude": [
"node_modules/jimp/**"
]
}
{
"rules": {
// -- Strict errors --
// These lint rules are likely always a good idea.
// Force function overloads to be declared together. This ensures readers understand APIs.
"adjacent-overload-signatures": true,
// Do not allow the subtle/obscure comma operator.
"ban-comma-operator": true,
// Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
"no-namespace": true,
// Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
"no-parameter-reassignment": true,
// Force the use of ES6-style imports instead of /// <reference path=> imports.
"no-reference": true,
// Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
// code currently being edited (they may be incorrectly handling a different type case that does not exist).
"no-unnecessary-type-assertion": false,
// Disallow nonsensical label usage.
"label-position": true,
// Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
"no-conditional-assignment": true,
// Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
"no-construct": true,
// Do not allow super() to be called twice in a constructor.
"no-duplicate-super": true,
// Do not allow the same case to appear more than once in a switch block.
"no-duplicate-switch-case": true,
// Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
// rule.
"no-duplicate-variable": [true, "check-parameters"],
// Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
// instead use a separate variable name.
"no-shadowed-variable": true,
// Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
"no-empty": [true, "allow-empty-catch"],
// Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
// This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
"no-floating-promises": true,
// Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
// deployed.
"no-implicit-dependencies": true,
// The 'this' keyword can only be used inside of classes.
"no-invalid-this": true,
// Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
"no-string-throw": true,
// Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
"no-unsafe-finally": true,
// Do not allow variables to be used before they are declared.
"no-use-before-declare": true,
// Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
"no-void-expression": [true, "ignore-arrow-function-shorthand"],
// Makes sure result of typeof is compared to correct string values.
"typeof-compare": true,
// Disallow duplicate imports in the same file.
"no-duplicate-imports": true,
// -- Strong Warnings --
// These rules should almost never be needed, but may be included due to legacy code.
// They are left as a warning to avoid frustration with blocked deploys when the developer
// understand the warning and wants to deploy anyway.
// Warn when an empty interface is defined. These are generally not useful.
"no-empty-interface": {"severity": "warning"},
// Warn when an import will have side effects.
"no-import-side-effect": {"severity": "warning"},
// Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
// most values and let for values that will change.
"no-var-keyword": {"severity": "warning"},
// Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
"triple-equals": {"severity": "warning"},
// Warn when using deprecated APIs.
"deprecation": {"severity": "warning"},
// -- Light Warnigns --
// These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
// if TSLint supported such a level.
// prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
// (Even better: check out utils like .map if transforming an array!)
"prefer-for-of": {"severity": "warning"},
// Warns if function overloads could be unified into a single function with optional or rest parameters.
"unified-signatures": {"severity": "warning"},
// Warns if code has an import or variable that is unused.
"no-unused-variable": {"severity": "warning"},
// Prefer const for values that will not change. This better documents code.
"prefer-const": {"severity": "warning"},
// Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
"trailing-comma": {"severity": "warning"}
},
"defaultSeverity": "error"
}
This diff is collapsed.