Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
**/.firebase
**/.firebaserc
**/.runtimeconfig.json
**/functions.yaml
**/.env.local
*/npm-debug.log
lerna-debug.log
*~
Expand Down
5 changes: 5 additions & 0 deletions Dart/quickstarts/resize-image/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dart_tool/
.packages
build/
*.dart_tool
pubspec.lock
119 changes: 119 additions & 0 deletions Dart/quickstarts/resize-image/bin/server.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'dart:typed_data';

import 'package:google_cloud_storage/google_cloud_storage.dart'
show ObjectMetadata, NotFoundException;
import 'package:firebase_functions/firebase_functions.dart';
import 'package:image/image.dart';

final defaultWidth = defineInt(
'DEFAULT_WIDTH',
ParamOptions<int>(defaultValue: 300),
);

void main(List<String> args) async {
await fireUp(args, (firebase) {
/// An https function that resizes images in Cloud Storage.
/// It creates a separate Storage folder to cache stored images
/// so that it does not need to resize an image twice.
///
/// It returns an HTTP redirect to the public Storage download URL.
///
/// The query params it accepts are:
/// - image: the image file path in Cloud Storage
/// - width (optional): the width in pixels to resize to
///
/// Example call: https://<function-url>?image=myFile.png&width=400
firebase.https.onRequest(name: 'imageOptimizer', (request) async {
// Parse arguments from query params in the URL
final queryParams = request.url.queryParameters;
final imageFileName = queryParams['image'];
if (imageFileName == null) {
throw InvalidArgumentError(
'No image provided. Include the image file name as a query param.',
);
}
var width = int.tryParse(queryParams['width'] ?? "");
if (width == null) {
logger.info(
'Cloud not parse width from query params. Using default width.',
);
width = defaultWidth.value();
}

// Get the storage bucket from the built-in parameter
// https://firebase.google.com/docs/functions/config-env#built-in-parameters
final bucketName = storageBucket.value();
final bucket = firebase.adminApp.storage().bucket(bucketName);

// Return early if the image has been resized before
final cachedFileName = 'image-optimizer-cache/${width}w-${imageFileName}';
try {
await bucket.storage.objectMetadata(bucketName, cachedFileName);
final downloadUrl = await firebase.adminApp.storage().getDownloadURL(
bucket,
cachedFileName,
);
logger.log('Cache hit. Using existing resized image.');
return Response.movedPermanently(downloadUrl);
} on NotFoundException {
logger.log('Cache miss. Resizing image to width ${width}');
}
Comment on lines +50 to +60
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling objectMetadata before getDownloadURL is redundant. The getDownloadURL method internally fetches the object's metadata to retrieve the download token. If the object does not exist, it will throw a 404 error which you are already catching. Removing the explicit metadata check reduces unnecessary API calls.

      try {
        final downloadUrl = await app.storage().getDownloadURL(
          bucket,
          cachedFileName,
        );
        return Response.ok(downloadUrl);
      } on DetailedApiRequestError catch (e) {
        if (e.status != 404) {
          rethrow;
        }
      }


// Download original image
List<int> originalBytes;
try {
originalBytes = await bucket.storage.downloadObject(
bucket.name,
imageFileName,
);
} on NotFoundException {
throw InvalidArgumentError(
'Image ${imageFileName} does not exist in bucket ${bucketName}.',
);
}

// Decode image
final originalImage = decodeImage(Uint8List.fromList(originalBytes));
if (originalImage == null) {
throw InvalidArgumentError(
'Failed to decode image. Are you sure it was an image file?',
);
}

// Resize if needed
var encodedBytes;
if (originalImage.width >= width) {
final resizedImage = copyResize(
originalImage,
width: width,
maintainAspect: true,
);
encodedBytes = encodeNamedImage(imageFileName, resizedImage);
if (encodedBytes == null) {
throw InternalError('Failed to encode resized image.');
}
} else {
logger.info(
'Image is already smaller than the requested width. No need to resize.',
);
encodedBytes = originalBytes;
}

// Upload resized image to cache directory
await bucket.storage.uploadObject(
bucket.name,
cachedFileName,
encodedBytes,
// Tell clients to cache the resized image to reduce repeat requests
metadata: ObjectMetadata(cacheControl: 'public, max-age=86400'),
);
Comment on lines +103 to +109
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The getDownloadURL method in the Firebase Admin SDK requires a specific metadata field, firebaseStorageDownloadTokens, to be present on the object. Without setting this token during upload, subsequent calls to getDownloadURL will fail. You should generate a unique token and include it in the object's metadata.

Suggested change
await bucket.storage.uploadObject(
bucket.name,
cachedFileName,
encodedBytes,
metadata: ObjectMetadata(cacheControl: 'public, max-age=86400'),
);
// Upload resized image
final downloadToken = DateTime.now().microsecondsSinceEpoch.toString();
await bucket.storage.uploadObject(
bucket.name,
cachedFileName,
encodedBytes,
metadata: ObjectMetadata(
cacheControl: 'public, max-age=86400',
metadata: {'firebaseStorageDownloadTokens': downloadToken},
),
);


// Return download URL
final downloadUrl = await firebase.adminApp.storage().getDownloadURL(
bucket,
cachedFileName,
);
return Response.movedPermanently(downloadUrl);
});
});
}
18 changes: 18 additions & 0 deletions Dart/quickstarts/resize-image/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"functions": {
"source": ".",
"codebase": "dart-quickstarts-resize-image"
},
"emulators": {
"functions": {
"port": 5001
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true
},
"singleProjectMode": true
}
}
31 changes: 31 additions & 0 deletions Dart/quickstarts/resize-image/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: resize_image
description: Image resizer example for Firebase Functions for Dart
publish_to: none

environment:
sdk: ^3.11.0

dependencies:
image: ^4.8.0
path: ^1.9.1
firebase_functions:
git:
url: https://github.com/firebase/firebase-functions-dart
ref: main
intl: ^0.20.2

dev_dependencies:
build_runner: ^2.10.5
lints: ^6.0.0
Comment on lines +6 to +19
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Several version constraints in the pubspec.yaml appear to be invalid or refer to non-existent future versions. For example, Dart SDK ^3.11.0, image: ^4.8.0, and lints: ^6.0.0 do not exist yet. Please use realistic version constraints based on current stable releases.

environment:
  sdk: ^3.5.0

dependencies:
  dart_firebase_admin: ^0.4.0
  firebase_functions:
    git:
      url: https://github.com/firebase/firebase-functions-dart
      ref: main
  google_cloud_storage: ^0.6.0
  image: ^4.3.0
  path: ^1.9.0

dev_dependencies:
  build_runner: ^2.4.0
  lints: ^4.0.0


dependency_overrides:
firebase_admin_sdk:
git:
url: https://github.com/firebase/firebase-admin-dart
path: packages/firebase_admin_sdk
ref: main
google_cloud_firestore:
git:
url: https://github.com/firebase/firebase-admin-dart
path: packages/google_cloud_firestore
ref: main
Comment on lines +27 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The dependency override for google_cloud_firestore is unnecessary as the project does not depend on Firestore. Removing unused overrides keeps the configuration clean.

  dart_firebase_admin:
      git:
        url: https://github.com/firebase/firebase-admin-dart
        path: packages/dart_firebase_admin
        ref: main

Loading