Skip to content

Commit 6e1f4fc

Browse files
kevinthecheunglaurenzlong
authored andcommitted
[firestore-shorten-urls-bitly] Refactor service-agnostic URL shortener code into sub directory (#45)
1 parent a59be4d commit 6e1f4fc

File tree

6 files changed

+346
-219
lines changed

6 files changed

+346
-219
lines changed

firestore-shorten-urls-bitly/extension.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ contributors:
3333
- authorName: Chris Bianca
3434
email: chris@csfrequency.com
3535
url: https://github.com/chrisbianca
36+
- authorName: Kevin Cheung
37+
email: kevincheung@google.com
38+
url: https://github.com/kevinthecheung
3639

3740
roles:
3841
- role: datastore.user
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"use strict";
2+
/*
3+
* Copyright 2019 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19+
return new (P || (P = Promise))(function (resolve, reject) {
20+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23+
step((generator = generator.apply(thisArg, _arguments || [])).next());
24+
});
25+
};
26+
Object.defineProperty(exports, "__esModule", { value: true });
27+
const admin = require("firebase-admin");
28+
const logs = require("./logs");
29+
var ChangeType;
30+
(function (ChangeType) {
31+
ChangeType[ChangeType["CREATE"] = 0] = "CREATE";
32+
ChangeType[ChangeType["DELETE"] = 1] = "DELETE";
33+
ChangeType[ChangeType["UPDATE"] = 2] = "UPDATE";
34+
})(ChangeType || (ChangeType = {}));
35+
class FirestoreUrlShortener {
36+
constructor(urlFieldName, shortUrlFieldName) {
37+
this.urlFieldName = urlFieldName;
38+
this.shortUrlFieldName = shortUrlFieldName;
39+
this.logs = logs;
40+
this.urlFieldName = urlFieldName;
41+
this.shortUrlFieldName = shortUrlFieldName;
42+
// Initialize the Firebase Admin SDK
43+
admin.initializeApp();
44+
}
45+
onDocumentWrite(change) {
46+
return __awaiter(this, void 0, void 0, function* () {
47+
this.logs.start();
48+
if (this.urlFieldName === this.shortUrlFieldName) {
49+
this.logs.fieldNamesNotDifferent();
50+
return;
51+
}
52+
const changeType = this.getChangeType(change);
53+
switch (changeType) {
54+
case ChangeType.CREATE:
55+
yield this.handleCreateDocument(change.after);
56+
break;
57+
case ChangeType.DELETE:
58+
this.handleDeleteDocument();
59+
break;
60+
case ChangeType.UPDATE:
61+
yield this.handleUpdateDocument(change.before, change.after);
62+
break;
63+
default: {
64+
throw new Error(`Invalid change type: ${changeType}`);
65+
}
66+
}
67+
this.logs.complete();
68+
});
69+
}
70+
extractUrl(snapshot) {
71+
return snapshot.get(this.urlFieldName);
72+
}
73+
;
74+
getChangeType(change) {
75+
if (!change.after.exists) {
76+
return ChangeType.DELETE;
77+
}
78+
if (!change.before.exists) {
79+
return ChangeType.CREATE;
80+
}
81+
return ChangeType.UPDATE;
82+
}
83+
;
84+
handleCreateDocument(snapshot) {
85+
return __awaiter(this, void 0, void 0, function* () {
86+
const url = this.extractUrl(snapshot);
87+
if (url) {
88+
this.logs.documentCreatedWithUrl();
89+
yield this.shortenUrl(snapshot);
90+
}
91+
else {
92+
this.logs.documentCreatedNoUrl();
93+
}
94+
});
95+
}
96+
;
97+
handleDeleteDocument() {
98+
this.logs.documentDeleted();
99+
}
100+
;
101+
handleUpdateDocument(before, after) {
102+
return __awaiter(this, void 0, void 0, function* () {
103+
const urlAfter = this.extractUrl(after);
104+
const urlBefore = this.extractUrl(before);
105+
if (urlAfter === urlBefore) {
106+
this.logs.documentUpdatedUnchangedUrl();
107+
}
108+
else if (urlAfter) {
109+
this.logs.documentUpdatedChangedUrl();
110+
yield this.shortenUrl(after);
111+
}
112+
else if (urlBefore) {
113+
this.logs.documentUpdatedDeletedUrl();
114+
yield this.updateShortUrl(after, admin.firestore.FieldValue.delete());
115+
}
116+
else {
117+
this.logs.documentUpdatedNoUrl();
118+
}
119+
});
120+
}
121+
;
122+
updateShortUrl(snapshot, url) {
123+
return __awaiter(this, void 0, void 0, function* () {
124+
this.logs.updateDocument(snapshot.ref.path);
125+
// Wrapping in transaction to allow for automatic retries (#48)
126+
yield admin.firestore().runTransaction((transaction => {
127+
transaction.update(snapshot.ref, this.shortUrlFieldName, url);
128+
return Promise.resolve();
129+
}));
130+
this.logs.updateDocumentComplete(snapshot.ref.path);
131+
});
132+
}
133+
;
134+
}
135+
exports.FirestoreUrlShortener = FirestoreUrlShortener;

firestore-shorten-urls-bitly/functions/lib/index.js

Lines changed: 28 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,114 +15,43 @@
1515
* limitations under the License.
1616
*/
1717
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1819
return new (P || (P = Promise))(function (resolve, reject) {
1920
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
2021
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21-
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
22+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
2223
step((generator = generator.apply(thisArg, _arguments || [])).next());
2324
});
2425
};
2526
Object.defineProperty(exports, "__esModule", { value: true });
26-
const admin = require("firebase-admin");
2727
const functions = require("firebase-functions");
2828
const bitly_1 = require("bitly");
29+
const abstract_shortener_1 = require("./abstract-shortener");
2930
const config_1 = require("./config");
3031
const logs = require("./logs");
31-
var ChangeType;
32-
(function (ChangeType) {
33-
ChangeType[ChangeType["CREATE"] = 0] = "CREATE";
34-
ChangeType[ChangeType["DELETE"] = 1] = "DELETE";
35-
ChangeType[ChangeType["UPDATE"] = 2] = "UPDATE";
36-
})(ChangeType || (ChangeType = {}));
37-
const bitly = new bitly_1.BitlyClient(config_1.default.bitlyAccessToken);
38-
// Initialize the Firebase Admin SDK
39-
admin.initializeApp();
40-
logs.init();
41-
exports.fsurlshortener = functions.handler.firestore.document.onWrite((change) => __awaiter(this, void 0, void 0, function* () {
42-
logs.start();
43-
if (config_1.default.urlFieldName === config_1.default.shortUrlFieldName) {
44-
logs.fieldNamesNotDifferent();
45-
return;
46-
}
47-
const changeType = getChangeType(change);
48-
switch (changeType) {
49-
case ChangeType.CREATE:
50-
yield handleCreateDocument(change.after);
51-
break;
52-
case ChangeType.DELETE:
53-
handleDeleteDocument();
54-
break;
55-
case ChangeType.UPDATE:
56-
yield handleUpdateDocument(change.before, change.after);
57-
break;
58-
default: {
59-
throw new Error(`Invalid change type: ${changeType}`);
60-
}
61-
}
62-
logs.complete();
32+
class FirestoreBitlyUrlShortener extends abstract_shortener_1.FirestoreUrlShortener {
33+
constructor(urlFieldName, shortUrlFieldName, bitlyAccessToken) {
34+
super(urlFieldName, shortUrlFieldName);
35+
this.bitly = new bitly_1.BitlyClient(bitlyAccessToken);
36+
logs.init();
37+
}
38+
shortenUrl(snapshot) {
39+
return __awaiter(this, void 0, void 0, function* () {
40+
const url = this.extractUrl(snapshot);
41+
logs.shortenUrl(url);
42+
try {
43+
const response = yield this.bitly.shorten(url);
44+
const { url: shortUrl } = response;
45+
logs.shortenUrlComplete(shortUrl);
46+
yield this.updateShortUrl(snapshot, shortUrl);
47+
}
48+
catch (err) {
49+
logs.error(err);
50+
}
51+
});
52+
}
53+
}
54+
const urlShortener = new FirestoreBitlyUrlShortener(config_1.default.urlFieldName, config_1.default.shortUrlFieldName, config_1.default.bitlyAccessToken);
55+
exports.fsurlshortener = functions.handler.firestore.document.onWrite((change) => __awaiter(void 0, void 0, void 0, function* () {
56+
return urlShortener.onDocumentWrite(change);
6357
}));
64-
const extractUrl = (snapshot) => {
65-
return snapshot.get(config_1.default.urlFieldName);
66-
};
67-
const getChangeType = (change) => {
68-
if (!change.after.exists) {
69-
return ChangeType.DELETE;
70-
}
71-
if (!change.before.exists) {
72-
return ChangeType.CREATE;
73-
}
74-
return ChangeType.UPDATE;
75-
};
76-
const handleCreateDocument = (snapshot) => __awaiter(this, void 0, void 0, function* () {
77-
const url = extractUrl(snapshot);
78-
if (url) {
79-
logs.documentCreatedWithUrl();
80-
yield shortenUrl(snapshot);
81-
}
82-
else {
83-
logs.documentCreatedNoUrl();
84-
}
85-
});
86-
const handleDeleteDocument = () => {
87-
logs.documentDeleted();
88-
};
89-
const handleUpdateDocument = (before, after) => __awaiter(this, void 0, void 0, function* () {
90-
const urlAfter = extractUrl(after);
91-
const urlBefore = extractUrl(before);
92-
if (urlAfter === urlBefore) {
93-
logs.documentUpdatedUnchangedUrl();
94-
}
95-
else if (urlAfter) {
96-
logs.documentUpdatedChangedUrl();
97-
yield shortenUrl(after);
98-
}
99-
else if (urlBefore) {
100-
logs.documentUpdatedDeletedUrl();
101-
yield updateShortUrl(after, admin.firestore.FieldValue.delete());
102-
}
103-
else {
104-
logs.documentUpdatedNoUrl();
105-
}
106-
});
107-
const shortenUrl = (snapshot) => __awaiter(this, void 0, void 0, function* () {
108-
const url = extractUrl(snapshot);
109-
logs.shortenUrl(url);
110-
try {
111-
const response = yield bitly.shorten(url);
112-
const { url: shortUrl } = response;
113-
logs.shortenUrlComplete(shortUrl);
114-
yield updateShortUrl(snapshot, shortUrl);
115-
}
116-
catch (err) {
117-
logs.error(err);
118-
}
119-
});
120-
const updateShortUrl = (snapshot, url) => __awaiter(this, void 0, void 0, function* () {
121-
logs.updateDocument(snapshot.ref.path);
122-
// Wrapping in transaction to allow for automatic retries (#48)
123-
yield admin.firestore().runTransaction((transaction => {
124-
transaction.update(snapshot.ref, config_1.default.shortUrlFieldName, url);
125-
return Promise.resolve();
126-
}));
127-
logs.updateDocumentComplete(snapshot.ref.path);
128-
});

firestore-shorten-urls-bitly/functions/lib/logs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
Object.defineProperty(exports, "__esModule", { value: true });
1818
const config_1 = require("./config");
19-
const obfuscatedConfig = Object.assign({}, config_1.default, { bitlyAccessToken: "********" });
19+
const obfuscatedConfig = Object.assign(Object.assign({}, config_1.default), { bitlyAccessToken: "********" });
2020
exports.complete = () => {
2121
console.log("Completed execution of extension");
2222
};

0 commit comments

Comments
 (0)