In my Angular application I have a contact form with a file upload input. The file upload input on the front-end side disallows to send a contact form if the files combined are more than 20 MB. Is there any way to achieve the same logic in Cloud Storage for Firebase? Currently I can only limit 20 MB, but for each file, i.e. if someone will upload 10 files, each 19 MB s/he won't be able to send a form, but the files will be send to my serverless back-end, which I don't want.
contact.component.html
<mat-form-field>
<!--
Accept only files in the following format: .doc, .docx, .jpg, .jpeg, .pdf, .png, .xls, .xlsx. However, this is easy to bypass, Cloud Storage rules has been set up on the back-end side.
-->
<ngx-mat-file-input
[accept]="[
'.doc',
'.docx',
'.jpg',
'.jpeg',
'.pdf',
'.png',
'.xls',
'.xlsx'
]"
(change)="uploadFile($event)"
formControlName="fileUploader"
multiple
aria-label="Here you can add additional files about your project, which can be helpeful for us."
placeholder="Additional files"
title="Additional files"
type="file"
>
</ngx-mat-file-input>
<mat-icon matSuffix>folder</mat-icon>
<mat-hint
>Accepted formats: DOC, DOCX, JPG, JPEG, PDF, PNG, XLS and XLSX,
maximum files upload size: 20 MB.
</mat-hint>
<!--
Non-null assertion operators are required to let know the compiler that this value is not empty and exists.
-->
<mat-error
*ngIf="contactForm.get('fileUploader')!.hasError('maxContentSize')"
>
This size is too large,
<strong
>maximum acceptable upload size is
{{
contactForm.get('fileUploader')?.getError('maxContentSize')
.maxSize | byteFormat
}}</strong
>
(uploaded size:
{{
contactForm.get('fileUploader')?.getError('maxContentSize')
.actualSize | byteFormat
}}).
</mat-error>
</mat-form-field>
contact.component.ts (size validator)
public maxFileSize = 20971520;
public contactForm: FormGroup = this.formBuilder.group({
fileUploader: [
'',
Validators.compose([
FileValidator.maxContentSize(this.maxFileSize),
Validators.maxLength(512),
Validators.minLength(2)
])
].toString()
})
contact.component.ts (file uploader)
/**
* @description Upload additional files to Cloud Firestore and get URL to the files.
* @param {event} - object of sent files.
* @returns {void}
*/
public uploadFile(event: any): void {
// Iterate through all uploaded files.
for (let i = 0; i < event.target.files.length; i++) {
const randomId = Math.random()
.toString(36)
.substring(2); // Create random ID, so the same file names can be uploaded to Cloud Firestore.
const file = event.target.files[i]; // Get each uploaded file.
// Get file reference.
const fileRef: AngularFireStorageReference = this.angularFireStorage.ref(
randomId
);
// Create upload task.
const task: AngularFireUploadTask = this.angularFireStorage.upload(
randomId,
file
);
// Upload file to Cloud Firestore.
task
.snapshotChanges()
.pipe(
finalize(() => {
fileRef.getDownloadURL().subscribe((downloadURL: string) => {
this.angularFirestore
.collection(process.env.FIRESTORE_COLLECTION_FILES!) // Non-null assertion operator is required to let know the compiler that this value is not empty and exists.
.add({ downloadURL: downloadURL });
this.downloadURL.push(downloadURL);
});
}),
catchError((error: any) => {
return throwError(error);
})
)
.subscribe();
}
}
storage.rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read; // Required in order to send this as attachment.
// Allow write files Firebase Storage, only if:
// 1) File is no more than 20MB
// 2) Content type is in one of the following formats: .doc, .docx, .jpg, .jpeg, .pdf, .png, .xls, .xlsx.
allow write: if request.resource.size <= 20 * 1024 * 1024
&& (request.resource.contentType.matches('application/msword')
|| request.resource.contentType.matches('application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|| request.resource.contentType.matches('image/jpg')
|| request.resource.contentType.matches('image/jpeg')
|| request.resource.contentType.matches('application/pdf')
|| request.resource.contentType.matches('image/png')
|| request.resource.contentType.matches('application/vnd.ms-excel')
|| request.resource.contentType.matches('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
}
}
}