Skip to content

Commit 971d16f

Browse files
committed
Upgrade all functions to Cloud Functions v2.
1 parent e955070 commit 971d16f

25 files changed

+1100
-938
lines changed

functions/.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = {
2121
},
2222
ignorePatterns: [
2323
'/lib/**/*', // Ignore built files.
24+
'.eslintrc.js', // Ignore this ESLint configuration file.
2425
],
2526
plugins: ['@typescript-eslint', 'import', 'prettier'],
2627
rules: {
@@ -38,4 +39,9 @@ module.exports = {
3839
},
3940
],
4041
},
42+
settings: {
43+
'import/resolver': {
44+
typescript: {},
45+
},
46+
},
4147
};

functions/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@
99
"prepare-deploy": "yarn build && yarn test && firebase functions:config:set discord.webhook=$DISCORD_WEBHOOK notification.url=$NOTIFICATION_URL jwt.secret=$JWT_SECRET",
1010
"deploy": "yarn prepare-deploy && firebase deploy --only functions",
1111
"logs": "firebase functions:log",
12-
"test": "yarn jest"
12+
"test": "yarn jest",
13+
"type-check": "tsc --noEmit"
1314
},
1415
"engines": {
1516
"node": "20"
1617
},
1718
"main": "lib/index.js",
1819
"dependencies": {
19-
"@google-cloud/pubsub": "^4.0.6",
20-
"@google-cloud/tasks": "^4.0.1",
20+
"@google-cloud/pubsub": "^4.11.0",
21+
"@google-cloud/tasks": "^6.0.1",
2122
"axios": "^0.21.1",
2223
"date-fns": "^4.1.0",
23-
"firebase-admin": "^11.11.0",
24-
"firebase-functions": "^4.4.1",
24+
"firebase-admin": "^13.3.0",
25+
"firebase-functions": "^6.3.2",
2526
"jsonwebtoken": "^9.0.2",
2627
"node-html-parser": "^6.1.13",
2728
"qs": "^6.11.2",
@@ -38,6 +39,7 @@
3839
"eslint": "^8.56.0",
3940
"eslint-config-google": "^0.14.0",
4041
"eslint-config-prettier": "^9.1.0",
42+
"eslint-import-resolver-typescript": "^4.3.4",
4143
"eslint-plugin-import": "^2.29.1",
4244
"eslint-plugin-prettier": "^5.0.1",
4345
"firebase-functions-test": "^0.2.0",
@@ -46,7 +48,7 @@
4648
"lint-staged": "^15.2.0",
4749
"prettier": "^3.1.1",
4850
"ts-jest": "^29.1.1",
49-
"typescript": "^5.2.2"
51+
"typescript": "^5.8.3"
5052
},
5153
"private": true,
5254
"husky": {

functions/src/abstract-command.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
import * as functions from 'firebase-functions';
2-
import * as admin from 'firebase-admin';
31
import { IResult } from './utils/types';
2+
import { Firestore } from 'firebase-admin/firestore';
3+
import { CallableRequest, CallableResponse } from 'firebase-functions/v2/https';
4+
import { Auth } from 'firebase-admin/auth';
45

56
abstract class AbstractCommand<R extends IResult = IResult> {
6-
protected db: admin.firestore.Firestore;
7+
db: Firestore;
8+
auth: Auth;
79

8-
constructor(db: admin.firestore.Firestore) {
10+
constructor(db: Firestore, auth: Auth) {
911
this.db = db;
12+
this.auth = auth;
1013
}
1114

1215
abstract execute(
13-
data: any,
14-
context: functions.https.CallableContext
16+
request: CallableRequest,
17+
response: CallableResponse | undefined
1518
): Promise<R>;
1619

1720
async checkUserIsAdministrator(uid: string): Promise<boolean> {
18-
const userRecord = await admin.auth().getUser(uid);
21+
const userRecord = await this.auth.getUser(uid);
1922
const email = userRecord.email;
2023
if (!email) {
2124
return false;

functions/src/admin/create-organization-command.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import {
99
ERROR_CREATING_ORGANIZATION_FAILED,
1010
IResult,
1111
} from '../utils/types';
12-
import * as functions from 'firebase-functions';
13-
import * as admin from 'firebase-admin';
12+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
1413

1514
export class CreateOrganizationCommand extends AbstractCommand<IResult> {
1615
@NeedAuthentication()
@@ -27,12 +26,12 @@ export class CreateOrganizationCommand extends AbstractCommand<IResult> {
2726
'memberEmailAddress',
2827
])
2928
async execute(
30-
data: any,
31-
_context: functions.https.CallableContext
29+
request: CallableRequest,
30+
_response: CallableResponse | undefined
3231
): Promise<IResult> {
3332
try {
34-
const memberEmailAddress = data.memberEmailAddress;
35-
const userRecord = await admin.auth().getUserByEmail(memberEmailAddress);
33+
const memberEmailAddress = request.data.memberEmailAddress;
34+
const userRecord = await this.auth.getUserByEmail(memberEmailAddress);
3635
if (
3736
!userRecord.providerData.some(
3837
(data) => data.providerId === 'github.com'
@@ -49,14 +48,14 @@ export class CreateOrganizationCommand extends AbstractCommand<IResult> {
4948
.doc('v1')
5049
.collection('profiles')
5150
.add({
52-
name: data.name,
53-
description: data.description,
54-
website_url: data.websiteUrl,
55-
icon_image_url: data.iconImageUrl,
56-
contact_email_address: data.contactEmailAddress,
57-
contact_person_name: data.contactPersonName,
58-
contact_tel: data.contactTel,
59-
contact_address: data.contactAddress,
51+
name: request.data.name,
52+
description: request.data.description,
53+
website_url: request.data.websiteUrl,
54+
icon_image_url: request.data.iconImageUrl,
55+
contact_email_address: request.data.contactEmailAddress,
56+
contact_person_name: request.data.contactPersonName,
57+
contact_tel: request.data.contactTel,
58+
contact_address: request.data.contactAddress,
6059
members: [userRecord.uid],
6160
created_at: new Date(),
6261
updated_at: new Date(),

functions/src/admin/fetch-keyboard-definition-by-id-command.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import AbstractCommand from '../abstract-command';
2-
import * as functions from 'firebase-functions';
32
import {
43
ERROR_KEYBOARD_DEFINITION_NOT_FOUND,
54
IKeyboardDefinitionDetail,
@@ -10,7 +9,7 @@ import {
109
NeedAuthentication,
1110
ValidateRequired,
1211
} from '../utils/decorators';
13-
import * as admin from 'firebase-admin';
12+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
1413

1514
interface IFetchKeyboardDefinitionByIdCommandResult extends IResult {
1615
keyboardDefinitionDetail?: IKeyboardDefinitionDetail;
@@ -21,25 +20,25 @@ export class FetchKeyboardDefinitionByIdCommand extends AbstractCommand<IFetchKe
2120
@NeedAdministratorPermission()
2221
@ValidateRequired(['id'])
2322
async execute(
24-
data: any,
25-
_context: functions.https.CallableContext
23+
request: CallableRequest,
24+
_response: CallableResponse | undefined
2625
): Promise<IFetchKeyboardDefinitionByIdCommandResult> {
2726
const documentSnapshot = await this.db
2827
.collection('keyboards')
2928
.doc('v2')
3029
.collection('definitions')
31-
.doc(data.id)
30+
.doc(request.data.id)
3231
.get();
3332
if (!documentSnapshot.exists) {
3433
return {
3534
success: false,
3635
errorCode: ERROR_KEYBOARD_DEFINITION_NOT_FOUND,
37-
errorMessage: `Keyboard Definition not found: ${data.id}`,
36+
errorMessage: `Keyboard Definition not found: ${request.data.id}`,
3837
};
3938
}
40-
const userRecord = await admin
41-
.auth()
42-
.getUser(documentSnapshot.data()!.author_uid);
39+
const userRecord = await this.auth.getUser(
40+
documentSnapshot.data()!.author_uid
41+
);
4342
const providerData = userRecord.providerData[0];
4443
return {
4544
success: true,

functions/src/admin/fetch-keyboard-definition-list-by-status-command.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import AbstractCommand from '../abstract-command';
2-
import * as functions from 'firebase-functions';
32
import { IKeyboardDefinition, IResult } from '../utils/types';
43
import {
54
NeedAdministratorPermission,
65
NeedAuthentication,
76
ValidateIncludes,
87
ValidateRequired,
98
} from '../utils/decorators';
9+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
1010

1111
interface IFetchKeyboardDefinitionListByStatusCommandResult extends IResult {
1212
keyboardDefinitionList: IKeyboardDefinition[];
@@ -20,14 +20,14 @@ export class FetchKeyboardDefinitionListByStatusCommand extends AbstractCommand<
2020
status: ['draft', 'in_review', 'rejected', 'approved'],
2121
})
2222
async execute(
23-
data: any,
24-
_context: functions.https.CallableContext
23+
request: CallableRequest,
24+
_response: CallableResponse | undefined
2525
): Promise<IFetchKeyboardDefinitionListByStatusCommandResult> {
2626
const querySnapshot = await this.db
2727
.collection('keyboards')
2828
.doc('v2')
2929
.collection('definitions')
30-
.where('status', '==', data.status)
30+
.where('status', '==', request.data.status)
3131
.orderBy('updated_at', 'desc')
3232
.get();
3333
return {

functions/src/admin/fetch-keyboard-definition-stats-command.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
12
import AbstractCommand from '../abstract-command';
23
import {
34
NeedAdministratorPermission,
@@ -8,7 +9,6 @@ import {
89
IResult,
910
KeyboardDefinitionStatus,
1011
} from '../utils/types';
11-
import * as functions from 'firebase-functions';
1212

1313
interface IFetchKeyboardDefinitionStatsResult extends IResult {
1414
totalCount?: number;
@@ -22,8 +22,8 @@ export class FetchKeyboardDefinitionStatsCommand extends AbstractCommand<IFetchK
2222
@NeedAuthentication()
2323
@NeedAdministratorPermission()
2424
async execute(
25-
_data: any,
26-
_context: functions.https.CallableContext
25+
_request: CallableRequest,
26+
_response: CallableResponse | undefined
2727
): Promise<IFetchKeyboardDefinitionStatsResult> {
2828
const querySnapshot = await this.db
2929
.collection('keyboards')

functions/src/admin/fetch-organization-by-id-command.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as functions from 'firebase-functions';
21
import {
32
ERROR_ORGANIZATION_NOT_FOUND,
43
IOrganization,
@@ -11,7 +10,7 @@ import {
1110
NeedAuthentication,
1211
ValidateRequired,
1312
} from '../utils/decorators';
14-
import * as admin from 'firebase-admin';
13+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
1514

1615
interface IFetchOrganizationByIdCommandResult extends IResult {
1716
organization?: IOrganization;
@@ -23,20 +22,20 @@ export class FetchOrganizationByIdCommand extends AbstractCommand<IFetchOrganiza
2322
@NeedAdministratorPermission()
2423
@ValidateRequired(['id'])
2524
async execute(
26-
data: any,
27-
_context: functions.https.CallableContext
25+
request: CallableRequest,
26+
_response: CallableResponse | undefined
2827
): Promise<IFetchOrganizationByIdCommandResult> {
2928
const documentSnapshot = await this.db
3029
.collection('organizations')
3130
.doc('v1')
3231
.collection('profiles')
33-
.doc(data.id)
32+
.doc(request.data.id)
3433
.get();
3534
if (!documentSnapshot.exists) {
3635
return {
3736
success: false,
3837
errorCode: ERROR_ORGANIZATION_NOT_FOUND,
39-
errorMessage: `Organization not found: ${data.id}`,
38+
errorMessage: `Organization not found: ${request.data.id}`,
4039
};
4140
}
4241
const organization: IOrganization = {
@@ -55,7 +54,7 @@ export class FetchOrganizationByIdCommand extends AbstractCommand<IFetchOrganiza
5554
};
5655
const members: IOrganizationMember[] = [];
5756
for (const memberUid of organization.members) {
58-
const userRecord = await admin.auth().getUser(memberUid);
57+
const userRecord = await this.auth.getUser(memberUid);
5958
members.push({
6059
uid: memberUid,
6160
email: userRecord.email!,

functions/src/admin/fetch-organizations-command.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {
1010
NeedAdministratorPermission,
1111
NeedAuthentication,
1212
} from '../utils/decorators';
13-
import * as functions from 'firebase-functions';
14-
import * as admin from 'firebase-admin';
13+
import { CallableRequest, CallableResponse } from 'firebase-functions/https';
1514

1615
export interface IFetchOrganizationsResult extends IResult {
1716
organizations?: IOrganizationWithMembers[];
@@ -21,8 +20,8 @@ export class FetchOrganizationsCommand extends AbstractCommand<IFetchOrganizatio
2120
@NeedAuthentication()
2221
@NeedAdministratorPermission()
2322
async execute(
24-
_data: any,
25-
_context: functions.https.CallableContext
23+
_request: CallableRequest,
24+
_response: CallableResponse | undefined
2625
): Promise<IFetchOrganizationsResult> {
2726
try {
2827
const organizationsQueryDocumentSnapshot = await this.db
@@ -51,7 +50,7 @@ export class FetchOrganizationsCommand extends AbstractCommand<IFetchOrganizatio
5150
for (const organization of organizations) {
5251
const members: IOrganizationMember[] = [];
5352
for (const memberUid of organization.members) {
54-
const userRecord = await admin.auth().getUser(memberUid);
53+
const userRecord = await this.auth.getUser(memberUid);
5554
members.push({
5655
uid: memberUid,
5756
email: userRecord.email!,

functions/src/admin/review.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import * as functions from 'firebase-functions';
2-
import { firestore } from 'firebase-admin';
31
import { notifyReviewStatusChangeMessageToDiscordAndGAS } from '../utils/notification';
2+
import { CloudEvent } from 'firebase-functions';
3+
import { MessagePublishedData } from 'firebase-functions/pubsub';
4+
import { Firestore, DocumentSnapshot } from 'firebase-admin/firestore';
5+
import { Auth } from 'firebase-admin/auth';
46

57
export const review = async (
6-
message: functions.pubsub.Message,
7-
db: firestore.Firestore
8+
message: CloudEvent<MessagePublishedData>,
9+
db: Firestore,
10+
auth: Auth
811
): Promise<void> => {
9-
const data = JSON.parse(Buffer.from(message.data, 'base64').toString());
12+
const data = JSON.parse(
13+
Buffer.from(message.data.message.data, 'base64').toString()
14+
);
1015
const definitionId = data.definitionId;
1116

1217
const definitionDocumentSnapshot = await db
@@ -46,6 +51,7 @@ export const review = async (
4651
});
4752
if (sameProductNameExists) {
4853
await reject(
54+
auth,
4955
definitionDocumentSnapshot,
5056
'The same keyboard definition (Vendor ID, Product ID and Product Name) already exists.'
5157
);
@@ -54,27 +60,30 @@ export const review = async (
5460
requestIsUnique(definitionDocumentSnapshot);
5561
};
5662

57-
const requestIsUnique = (
58-
definitionDocument: firestore.DocumentSnapshot
59-
): void => {
63+
const requestIsUnique = (definitionDocument: DocumentSnapshot): void => {
6064
const data = definitionDocument.data()!;
6165
const message = `The Vendor ID, Product ID and Product Name of the keyboard ${data.name}(${data.product_name}) (${definitionDocument.id}) is unique.`;
6266
console.log(message);
6367
};
6468

6569
const reject = async (
66-
definitionDocument: firestore.DocumentSnapshot,
70+
auth: Auth,
71+
definitionDocument: DocumentSnapshot,
6772
reason: string
6873
): Promise<void> => {
6974
await definitionDocument.ref.update({
7075
status: 'rejected',
7176
reject_reason: reason,
7277
updated_at: new Date(),
7378
});
74-
await notifyReviewStatusChangeMessageToDiscordAndGAS(definitionDocument.id, {
75-
name: definitionDocument.data()!.name,
76-
author_uid: definitionDocument.data()!.author_uid,
77-
product_name: definitionDocument.data()!.product_name,
78-
status: 'rejected',
79-
});
79+
await notifyReviewStatusChangeMessageToDiscordAndGAS(
80+
auth,
81+
definitionDocument.id,
82+
{
83+
name: definitionDocument.data()!.name,
84+
author_uid: definitionDocument.data()!.author_uid,
85+
product_name: definitionDocument.data()!.product_name,
86+
status: 'rejected',
87+
}
88+
);
8089
};

0 commit comments

Comments
 (0)