I my case I use ParseSwift cloud function to save file:
struct CloudSaveFile: ParseCloud {
//Return type of your Cloud Function
typealias ReturnType = String
//These are required for Object
var functionJobName: String = CloudFunctionType.saveFile.rawValue
init(name: String, data: [UInt8], ownerId: String) {
self.name = name
self.data = data
self.ownerId = ownerId
}
var name: String?
var data: [UInt8]?
var ownerId: String?
}
I the picture data are encrypted with a key string equal to profile objectId
so that the images cannot be viewed via public direct link in the storage (there is the profile objectId
not known). The cloud function creates a reference in the MongoDB and returns to the client the new image url:
//File does not have final url and name in beforeFileSave, can't be used to save FileObj
//in afterFileSave I cannot remove metadata and the ownerId should be inaccessible
//Using cloud function with passing ownerId as a parameter
Parse.Cloud.define("saveFile", async (request) => {
//check if user is authenticated
const token = { sessionToken: request.user.getSessionToken() };
if (token == undefined) {
throw `could not get valid session token`;
}
//save file to recieve url an unique name
const passedName = request.params.name;
const bytes = request.params.data;
const file = new Parse.File(passedName, bytes);
await file.save({sessionToken: token});
//create FileObj entry with ownerID
const fileObject = new Parse.Object('FileObj');
fileObject.set('file', file);
fileObject.set('fn', file.name().split('_')[0]); //getting only uid part
fileObject.set('ow', request.params.ownerId);
await fileObject.save(null, { useMasterKey: true });
//returning to the clien, what is the new image url
return file.url();
});
It is then easy to work with object in the database, in this case FileObj
. For example when it gets deleted it clears the related file from the storage:
//FileObj gets deleted first and triggers delete in storage
Parse.Cloud.beforeDelete("FileObj", async (request) => {
const file = request.object.get("file");
//request.log.info(`deleting file: ${file.name()}`);
if (file == undefined) {
return;
}
try {
await file.destroy({ useMasterKey: true }); //here should be probably return instead await??
} catch (e) {
//TODO: mark somewhere for failed indexing attempts?
throw `error deleting file in storage. File: ${file.name()}, error: ${e}`;
}
return;
});
Note to the idea behind encrypting all profile images with the given profile objectId
as key:
- Users that search for other users have to be already logged in to obtain both the other’s
objectId
and file urls
- The storage direct links are not readable for any search engines as they do not know to what profile objectId the file belongs
I hope it helps. Let me know your thoughts as I myself am learning software development from scratch.
Thanks!